Está en la página 1de 152

CURSO

DE COMPUTACIÓN CIENTÍFICA

Versión 3.5 – Septiembre 2019






Universidad de La Laguna
Departamento de Astrofísica


Jorge A. Pérez Prieto, Teodoro Roca Cortés y César Esteban López
Í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 . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5. Aritmética computacional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

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


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

3. Análisis de errores 25
3.1. Tipos de errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2. Propagación de errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3. Sensibilidad y Acondicionamiento de un problema . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.4. Estabilidad y Exactitud de un algoritmo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

4. Programas ejecutables 31
4.1. Reutilizando el código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2. Definiendo funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.3. Entrada de datos por pantalla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.4. Definiendo un módulo personal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.5. Estructura de un programa o script y normas de escritura . . . . . . . . . . . . . . . . . . . . . . . . 37
4.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

5. Control de flujo 41
5.1. El bucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.2. El bucle while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

I
5.3. Sentencias condicionadas if-else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.4. Declaraciones break y continue y sentencia else en bucles . . . . . . . . . . . . . . . . . . . . . . . 48
5.5. Una aplicación interesante: Raíces de ecuaciones algebraicas cualquiera . . . . . . . . . . . . . . . . 49
5.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

6. Probabilidad y números aleatorios 53


6.1. Algunas distribuciones de probabilidad: la Distibución Binomial . . . . . . . . . . . . . . . . . . . . 55
6.2. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

7. Cálculo numérico con Numpy 59


7.1. Listas y arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.2. Creando arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
7.3. Indexado de arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
7.4. Algunas propiedades de los arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
7.5. Operaciones con arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
7.6. Arrays multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
7.7. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

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


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

9. Lectura y escritura de ficheros 73


9.1. Creando un fichero sencillo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
9.2. Lectura de ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
9.3. Lectura y escritura de ficheros de datos con numpy . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
9.4. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

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


10.1. Trabajando con texto dentro del gráfico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10.2. Representación gráfica de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
10.3. Histogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
10.4. Figuras diferentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
10.5. Varios gráficos en una misma figura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
10.6. Representando datos experimentales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
10.7. Datos experimentales con barras de error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
10.8. Representación de datos bidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
10.9. Guardando las figuras creadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
10.10. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

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


11.1. Formulación general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
11.2. Aplicación al ajuste de funciones a datos experimentales . . . . . . . . . . . . . . . . . . . . . . . . 99
11.3. Ajuste a polinomios con Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
11.4. Ajuste de funciones no lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
11.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

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


12.1. La integración o cuadratura numérica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
12.2. Álgebra matricial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
12.3. Operaciones básicas con matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
12.4. Resolución de sistemas de ecuaciones lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
12.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

II
13. Apéndice A: Recursos informáticos para el curso 119
13.1. El sistema operativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
13.2. El lenguaje de programación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
13.3. Python y módulos científicos para Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
13.4. Editores de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
13.5. Más documentación y bibliografía . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

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


14.1. Empezando con Linux: el escritorio de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
14.2. Trabajando con la consola de Linux. Directorios y Ficheros. . . . . . . . . . . . . . . . . . . . . . . 124
14.3. Copiando, moviendo y renombrando ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
14.4. Caracteres comodín . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
14.5. Trabajando con ficheros de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
14.6. El sistema de usuarios y permisos de Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
14.7. Empaquetando y comprimiendo ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
14.8. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

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


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

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


16.1. Introducción a Sympy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
16.2. Operaciones algebraicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
16.3. Cálculo de límites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
16.4. Cálculo de derivadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
16.5. Expansión de series . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
16.6. Integración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
16.7. Ecuaciones algebraicas y álgebra lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
16.8. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

17. Historial de cambios 145


17.1. Revisión 3.5 - septiembre 2019 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
17.2. Revisión 3.4 - Septiembre 2018 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
17.3. Revisión 3.3 - Septiembre 2017 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
17.4. Revisión 3.2 - Septiembre 2016 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
17.5. Revisión 3.1 - Septiembre 2015 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
17.6. Versión 3.0 - Septiembre 2014 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
17.7. Versión 2.5 - Noviembre 2013 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146

III
IV
Curso de Computación Científica, Versión 2019-09-19

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


Edición 3.5
Fecha septiembre 19, 2019
Documentos PDF - ePUB (experimental)
Contenido

Índice general 1
Curso de Computación Científica, Versión 2019-09-19

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 observaciones, 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 fundamentales: 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 decir, encontrar algorit-
mos que converjan rápidamente hacia la solución verdadera (en el límite).
2. La forma de evaluar la aproximación de los resultados es decir, estimar la exactitud y/o precisión de las solu-
ciones aproximadas.
La simulación numérica o computacional. La simulación numérica es la representación y emulació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.

3
Curso de Computación Científica, Versión 2019-09-19

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 matemá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 perturba-
ciones 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.
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 finitas).
Sistemas de orden alto por otros de orden bajo.
Procesos infinitos por otros finitos (integrales por sumatorios, derivadas por diferencias finitas, 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 original, pero generalmente
podremos aproximarla tanto como queramos pagando el precio de más almacenamiento de datos y/o más trabajo de

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


Curso de Computación Científica, Versión 2019-09-19

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 olvidar o despreciar algu-
nos 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 OBSERVACIONES que
eviten o minimicen ruido o sesgos, o también completar una muestra insuficiente
Preparando COMPUTACIONES PREVIAS. Tratar de mejorar la precisión y exactitud 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,
DISCRETIZACIÓN, por ejemplo usando diferencias finitas en los valores de las variables, 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 significativas y/o indi-
cando 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 n , entonces su representación decimal tiene n 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.

1.3. Aproximaciones en Computación Científica 5


Curso de Computación Científica, Versión 2019-09-19

4. Todos los dígitos entre el más y el menos significativo son contados como cifras significativas.
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.
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 significativas 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 suficiente 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 representarlos; 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 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.

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


Curso de Computación Científica, Versión 2019-09-19

La precisión depende del propio número. Cuanto más próximos a 0 sean los números mejor 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 x = 0,012, con dos cifras significativas. Si y = exp(x), encontrar con cuántas cifras significativas
podemos evaluar y.

1.6. Ejercicios 7
Curso de Computación Científica, Versión 2019-09-19

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 eje-
cutable 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 ipython1 que se trata de una consola
de Python mejorada, incluyendo completado de funciones y variables, funcionalidad de los comandos básicos de la
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.
1 Mucha más información en la web oficial: http://ipython.org/

9
Curso de Computación Científica, Versión 2019-09-19

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 ipython2 %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 duran-
te la sesión. Con los comandos %logoff y %logon podemos, respectivamente, 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 (el que escribimos nosotros, no el que devuelve) 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
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, Versión 2019-09-19

(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 comando type() podemos
saber el tipo de dato que se asigna a una variable si en cualquier momento 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 puntos o espacios en blanco. Las tildes y letras como la ñ pueden usarse en Python
3, pero no son recomendables pues no se usan en otras lenguas y en versiones anteriores de Python. 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 funciona-
lidad entre enteros y decimales, es decir, entre int y float; aunque ambos sean valores numéricos, tienen propiedades
distintas que afectan al cálculo. Hay que tener en cuenta que al dividir números enteros el resultado puede ser diferente
si usamos Python 3 (la versión que utilizamos en el presente curso) o Python 2. En Python 3, si realizamos la división
de dos números enteros cuyo resultado no es entero, nos devolverá el valor correcto (un float):

>>> 113/27
4.185185185185185

Pero, en Python 2, 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, el resultado del ejemplo anterior ahora es diferente:

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

Esto puede dar lugar a problemas cuando realizamos programas o cálculos que involucren muchas operaciones ma-
temáticas con dicha versión. En Python 2, si queremos usar números decimales, también llamados de coma flotante,
debe emplearse directamente un valor decimal (float); en estos casos:

2.2. Tipos básicos de datos 11


Curso de Computación Científica, Versión 2019-09-19

>>> 113.0/27.0
4.185185185185185

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

Es decir, para Python 2 numero = 3 no es lo mismo que numero = 3.0 aunque ambos tengan el mismo valor.
De todas formas, como ya se indicó anteriormente, dicho problema no lo tendremos usando Python 3.
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

>>> str(34)
'34'

Para el caso de los float, se pueden redondear con round(), que redondea al entero más próximo. 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 interesantes y aprenderemos
cómo importarlos y a utilizarlos.

2.3 Operadores aritméticos y lógicos

Con python se pueden hacer las operaciones aritméticas habituales usando los símbolos correspondientes:

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
(continues on next page)

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


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


12.25
>>> x+x**2
15.75
>>> x**(2/3)
2.3052181460292234
>>> 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 concreto se usan:

Operacion Simbolo
Igual a (comparación) ==
Distinto de (comparación) !=
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
>>> 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:

2.4. Cadenas de texto 13


Curso de Computación Científica, Versión 2019-09-19

>>> # 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


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 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 3 son de tipo unicode de 8 bits, por lo que podemos usar acentos o la letra ñ sin problemas
en el contenido de las variables string. En Python 2 son de tipo ascii (7 bits) y daba problemas al usar letras o caracteres
ajenos al idioma inglés.

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


Curso de Computación Científica, Versión 2019-09-19

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

>>> print(frase)
Si he logrado ver más lejos, ha sido porque he subido a hombros de gigantes

En Python 2 nos hubiera dado un error al mostrar más.


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 MÁS LEJOS, HA SIDO PORQUE HE SUBIDO A HOMBROS DE GIGANTES

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


# la variable frase_minusculas
>>> print(frase_mayusculas)
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")
'Si he logrado ver más lejos, ha sido porque he
subido a la chepa de gigantes'

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 haciendo 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 más lejos, ha sido porque he
subido a la chepa de gigantes

Advertencia: Recordar que el índice en las cadenas de texto y en general cualquier lista de números, empie-
za 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 concatenar 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

2.5. Impresión de texto y de números 15


Curso de Computación Científica, Versión 2019-09-19

Una manera más práctica y correcta de hacer esto es imprimiendo los números con el formato que queramos; veamos
como hacerlo:
>>> from math import *
>>> print("%d elevado al cuadrado es %d" % (a, b))
10 elevado al cuadrado es 100
>>> # 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 punto, 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 significativos 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,1416 ± 0,0001 podemos expresarlo como:
>>> # 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 %.4f +/- %.4f" % (resultado, error))
El resultado del experimento es 3.1416 +/- 0.0001

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 estructu-
radas 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]]

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


Curso de Computación Científica, Versión 2019-09-19

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"

>>> 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 posició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():

>>> 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']
(continues on next page)

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


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)

>>> 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']

Un herramienta muy útil es usar la función range(), que Python 3 considera como un tipo de datos y que permite
crear una serie de números enteros en sucesión aritmética. Por ejemplo, para crear un lista de 10 elementos, de 0 a 9,
e imprimirlos podemos hacer esto:

>>> serie1 = list(range(10))


>>> print(serie1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Para mostrar por pantalla la serie de números creada debemos pasarla a lista con list(). Para comprobar que
range() es un tipo de datos podemos hacer:

>>> type(range(10))
<type 'range'>

Mientras que serie1 es efectivamente una lista:

>>> type(serie1)
<type 'list'>

Con range() se puede crear también una lista de números indicando el inicio, final y el intervalo entre dos consecu-
tivos. Por ejemplo, para crear una lista con números enteros de 100 a 200 a intervalos de 20 podemos escribir:

>>> serie2 = list(range(100, 200, 20))


>>> print(serie2)
[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:

>>> print(serie1+serie1)
[100, 120, 140, 160, 180, 100, 120, 140, 160, 180]
>>> print(serie1*3)
[100, 120, 140, 160, 180, 100, 120, 140, 160, 180, 100, 120, 140, 160, 180]

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


Curso de Computación Científica, Versión 2019-09-19

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 = list(range(1, 11)) # lista de números de 1 a 10 (11 no incluido)
>>> mitad = int(len(numeros)/2) # indice mitad de la lista (entero)
>>> 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:
>>> 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

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


Curso de Computación Científica, Versión 2019-09-19

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

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 adicionales muy intere-
santes. 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íci-
tamente, 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 las 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']

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


Curso de Computación Científica, Versión 2019-09-19

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 grandes 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.
Por nuestra parte iremos introduciendo más adelante otros módulos y paquetes numéricos de Python 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 funcio-
nan. 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. Como acabamos de ver, si queremos ver las
funciones disponibles en un módulo podemos hacerlo con dir(modulo); de manera similar, si queremos conocer
los métodos disponible para un tipo de dato en concreto, podemos usar help() con una variable de ese tipo. Por
ejemplo, ya que frase es un string, podemos saber los métodos que se pueden aplicar a cualquier string con:
>>> help(frase)

lo que nos devolverá una lista con todos los métodos disponibles para cualquier string. Si usamos ipython, podemos
hacerlo de manera rápida 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.
Para salir del entorno de ayuda teclear q.
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.9 Ejercicios

1. Evaluar en punto flotante las siguientes funciones para los valores de x=-0.5, 1.1, 2, 3.1:

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


Curso de Computación Científica, Versión 2019-09-19

x4 + x3 + 2x2 x + 3
p
4x 3 | sin x + tan x|
x3 5x
(x2 + 12 )2

2. 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)
3. 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))
4. 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 palabras?
Pasa a una variable los 15 primeros carácteres. Pasa a una lista las cinco primeras palabras.
¿Cuántas letras tiene la última palabra?
Concatenen (unan) el primer tercio de la frase con el último tercio.
5. 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 +).
6. 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.
7. 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:

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


Curso de Computación Científica, Versión 2019-09-19

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 (concatenados, 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 elementos).
8. 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.
9. El valor de ⇡ se puede obtener del módulo math con pi. Imprimir su valor mostrando sólo tres decimales.
10. 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.
11. 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 alfabeticamente.
12. 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 gaseosos (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.

2.9. Ejercicios 23
Curso de Computación Científica, Versión 2019-09-19

24 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 f (x), donde tenemos que:
x, es valor verdadero de la variable de entrada,
y = f (x), es el valor verdadero deseado de salida,
xi , es valor inexacto o aproximado de entrada,
fa , es la aproximación mejor (algoritmo) a la función f ,
podemos calcular que el valor del error absoluto total acumulado en el cálculo será:

ya y = fa (xi ) f (x) = [fa (xi ) f (xi )] + [f (xi ) f (x)] ,

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 supone poner un límite al número de dígitos decimales por lo que, en definitiva, produce un
redondeo hacia abajo. El error de truncamiento puede ser hasta el doble del error máximo de redondeo.

25
Curso de Computación Científica, Versión 2019-09-19

2. Error de redondeo. Es la diferencia entre el resultado obtenido con un algoritmo con aritmética 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 sen(⇡/10) y los errores (computacional, de los datos y el total) que cometen
tomando como dato de entrada (xi ) el valor de ⇡ ⇡ 3 y el del algoritmo aproximado (fa ) como sen(x) ⇡ x.
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 sen(⇡/50) o sen(⇡/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 calidad 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 y = f (x) para un cierto valor de x dado, y sólo podemos obtener
un valor aproximado ya , utilizando un algoritmo fa ; entonces podemos estimar el error absoluto que cometemos
definiendo ERROR ADELANTE como y = ya y.
No obstante, no siempre es posible calcularlo y, a veces, debemos resolver el problema de otra forma: obtener la
solución (aproximada) al problema original podría hacerse obteniendo la solución exacta de un problema “”modifi-
cado”“, 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 x = xi x, donde f (xi ) = ya ,
como el ERROR ATRÁS; es decir, cuánto error en los datos de entrada podemos “admitir” para explicar todo el error
obtenido 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 eligiremos el algoritmo o
procedimiento que nos de menor error.
p
Ejemplo: Queremos calcular la raíz cuadrada de 2, y = 2, con la aproximación ya = 1,41. Calculen los errores
hacia adelante y hacia atrás.
Solución: haciapadelante: | y| = |ya y| = |1,41 1,4142...| = 0,0042. Para calcular el error hacia atrás tenemos
en cuenta que 1,9881 = 1,41 y que xi = 1,9881, entonces | x| = |xi x| = |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.

26 Capítulo 3. Análisis de errores


Curso de Computación Científica, Versión 2019-09-19

Si tenemos dos magnitudes r y s (cuyos errores relativos son Rr y Rs ) y las medimos con errores absolutos de ry
s , con r ⌧ r y s ⌧ s, si efectuamos su producto Q = r · s, lo obtendremos con un error:

Q = r · s = rv (1 + Rr ) · sv (1 + Rs ) = rv sv (1 + Rr + Rs + Rr Rs ) ⇡ rv sv (1 + Rr + Rs ) = Qv (1 + RQ ) ,

con lo que tenemos finalmente RQ = Rr + Rs .


Ustedes mismos pueden comprobar que, de forma similar, se puede mostrar que si:
Q = r · s · t · u · v . . . entonces RQ = Rr + Rs + Rt + Ru + Rv + . . . ,
Q = rn entonces RQ = nRr , para n 0.
No obstante podrían comprobar igualmente que en el caso de una división, es decir si Q = r/s, entonces RQ =
Rr Rs . 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, Rr y Rs pueden ser positivos o negativos (redefiniendo el valor
absoluto) con lo que RQ = Rr + Rs puede variar entre ( |Rr| |Rs|) y (|Rr| + |Rs|).
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 Q(r, s, t, ...). En este caso,
el error en Q, Q, en función de los errores r, s, t,. . . vendría dado por:
@Q @Q @Q
Q=| |· r+| |· s+| |· t + ...
@r @s @t
@Q
donde @r es la derivada parcial de Q respecto de r y análogamente las demás.

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:

| f (xif)(x)f (x) | | y
y |
= x
,
| (xix x) | | x |

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 C 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.

3.3. Sensibilidad y Acondicionamiento de un problema 27


Curso de Computación Científica, Versión 2019-09-19

De la definición anteriormente efectuada, y teniendo en cuenta la aproximación f (xi ) = f (x)+f 0 (x) x+... podemos
calcular C de forma aproximada como:
f 0 (x) x
f (x) xf 0 (x)
C=| x
|=| |
x
f (x)

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 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 solució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 y = sin(2!t + ), donde ! = 5,5Hz. Encontrar el
error relativo en y debido a un error relativo en t del 0.1 % cuando: t = 2!

s, y t = !⇡ s. Finalmente calculen los
errores relativos antedichos para el caso de = ⇡/3.
3 5
x7
2. Aproximemos la función sin(x) por su desarrollo en serie sin(x) ⇡ x x3! + x5! 7! + .... Se pide que calculen
los errores hacia adelante y hacia atrás para x = 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 Km3 .
Calculen finalmente su densidad y expliciten cuantas cifras significativas tiene el resultado final.
4. Cuando un electrón e , de masa me , se mueve a velocidad v en un acelerador sincrotrón su masa aumenta según
m = me [1 (v/c)2 ] (1/2) , donde me es su masa en reposo y c es la velocidad de la luz en el vacío. Se pide:
Demostrar que el incremento (error) relativo en m es aproximadamente (v/c)2 veces el incremento (error)
relativo en v, suponiendo que (v/c) ⌧ 1.
Calculen la masa del e si su velocidad es de 35500. Km/s y en cuánto aumenta si acelera hasta 36000.
Km/s.

28 Capítulo 3. Análisis de errores


Curso de Computación Científica, Versión 2019-09-19

5. Supongamos que medimos la aceleración de la gravedad g, 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 péndulo simple (midiendo su longitud `
y su periodo ⌧ ). Del resultado de la experiencia se obtiene que ` = 10,002 ± 0,0001m y ⌧ = 6,42559s ± 1µs.
Se pide:
Cuál es el error relativo que tendrá la determinación de g.
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 z = 8,1 ± 0,3.
Utilizando el efecto Doppler relativista nos permite calcular una velocidad de recesión de 288748 ± 2338Km/s
. 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 v = H · d, donde v es la velocidad de recesión del objeto en cuestión,
d es su distancia y H es la llamada constante de Hubble, una constante cuyo valor medido (en 2009 usando
el HST) es de 74,2 ± 3,6Km/s/M pc. 1M pc = 106 pc ; 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.

3.5. Ejercicios 29
Curso de Computación Científica, Versión 2019-09-19

30 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/python3
#-*- coding: utf-8 -*-

# Mensaje de bienvenida
print("Programa de cálculo del cubo de un número.\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 progra-
ma, escribiendo en ella

31
Curso de Computación Científica, Versión 2019-09-19

$ 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 aunque, como hemos visto, Python 3
los soporta por defecto debido a que usa caracteres unicode.
Ya que la primera línea indica en qué directorio está el ejecutable de python, el programa tambié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 ejecució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

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

In [1]: run cubo.py

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.:

32 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Versión 2019-09-19

# 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 utilizadas (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 utilizadas 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):
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


(continues on next page)

4.2. Definiendo funciones 33


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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 procedimientos. 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


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 definició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 condicio-
nales). 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


(continues on next page)

34 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


l,s,v = esfera(3.215)

# 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 input() de la forma siguiente (pueden probarlo en la consola de Python):
>>> entrada_numero = 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
# 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
(continues on next page)

4.3. Entrada de datos por pantalla 35


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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(input('Deme el radio de la esfera: ' ))

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


resultado = esfera(radio)

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

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(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:

# Introducimos dos valores float en dos variables diferentes


>>> x, y = eval(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 programa. Ya hemos visto
que la distribución estándar de python ofrece un buen número de módulos predefinidos que se agrupan normalmente

36 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Versión 2019-09-19

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
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áticamente 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 modifica
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 escritura

Al hacer un programa es siempre conveniente guardar una cierta estructura que ayudará a entenderlo 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 algorit-
mo solución del problema.

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


Curso de Computación Científica, Versión 2019-09-19

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 planteado.
Además de una correcta y ordenada estructura general del programa, es conveniente mantener ciertas 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 ejemplo básico para entender a lo que nos referimos es el
sangrado, que como hemos visto en Python es 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

4.6 Ejercicios

1. Escribir una función que calcule la distancia cartesiana entre dos puntos cualesquiera de coordenadas (x1 , y1 ) y
(x2 , y2 ).
2. Escribir un programa que calcule la densidad media de cualquier planeta, pidiendo como entrada su masa y su
radio medio.

38 Capítulo 4. Programas ejecutables


Curso de Computación Científica, Versión 2019-09-19

3. La variación de temperatura de un cuerpo a temperatura inicial T0 en un ambiente a Ts cambia de la siguiente


manera:
kt
T = Ts + (T0 Ts ) e

con t 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 pro-
blemas anteriores. Escribir un programa principal en el que las utilice y de esta forma comprueba si funciona
correctamente.

4.6. Ejercicios 39
Curso de Computación Científica, Versión 2019-09-19

40 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:

41
Curso de Computación Científica, Versión 2019-09-19

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 elementos (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 identificamos 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:
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.
Los bucles for nos permiten crear nuevas listas con resultados de cálculos fácilmente, por ejemplo:
# Importo la función log10 del módulo math
from math import log10

(continues on next page)

42 Capítulo 5. Control de flujo


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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 muyP
interesante del bucle for es la suma de series de números. Supongamos que queremos calcular el
10
sumatorio siguiente n=1 n12 . 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 "--------------------------------"

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("%2d %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

5.2. El bucle while 43


Curso de Computación Científica, Versión 2019-09-19

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
4
5

En este ejemplo se define inicialmente un valor 0 para la variable cuentas y su valor se va redefiniendo, 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 con un ejemplo que muestra por pantalla los números naturales menores que 5 :

x = 0
while not x == 5:
(continues on next page)

44 Capítulo 5. Control de flujo


Curso de Computación Científica, Versión 2019-09-19

Figura 1: Diagrama de flujo de una sentencia while.

(proviene de la página anterior)


print("x = %d" % x)
x = x + 1

""" Resultado que obtenemos del programa:


x = 0
x = 1
x = 2
x = 3
x = 4
"""

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)

""" Resultado que obtenemos:


x = 0.10000000000000001
x = 0.20000000000000001
x = 0.30000000000000004
x = 0.40000000000000002
x = 0.50000000000000000
x = 0.59999999999999998
(continues on next page)

5.2. El bucle while 45


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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 para 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á 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 operaciones o detener el proceso de cálculo.
Un ejemplo de su utilización es el siguiente:

46 Capítulo 5. Control de flujo


Curso de Computación Científica, Versión 2019-09-19

c = eval(input('Dame un número entero: ')) #Introducimos un número

if c>0: # comprueba si es positivo


print("El número es positivo")
elif c<0: # si no lo es, comprueba si es negativo
print("El número es negativo")
else: # Si nada de lo anterior se cumple, haz lo siguiente
print("El número es 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.

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

Advertencia: Es muy importante darse cuenta nuevamente los bloques de sangrado o espacios 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 podría ser:
a, b = eval(input('Dame dos números enteros diferentes: ')) #Introducimos números

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.

5.3. Sentencias condicionadas if-else 47


Curso de Computación Científica, Versión 2019-09-19

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 = eval(input('Dame dos números enteros diferentes: '))

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:

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


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

También podemos imponer varias condiciones a un if, aquí mostramos un par de ejemplos concretos con operadores
lógicos:

if (a > 10) and (b < 0): #Requerimos que uno sea mayor que 10 y el otro negativo

if (a == 0) or (b == 0): #Requerimos que uno de los números sea 0

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.
(continues on next page)

48 Capítulo 5. Control de flujo


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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 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, interrumpiendo 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 Una aplicación interesante: Raíces de ecuaciones algebraicas


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 trascendentes (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 f (x), continua en el intervalo [a, b] donde f (a) y f (b) 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 [a, b] por la mitad y llamemos m al punto medio. Si el signo de f (m) es
diferente al de f (a) aplicamos otra vez el método al intervalo [a, m]; si, por el contrario, el signo de f(m) es diferente al
de f(b) aplicamos otra vez el método al intervalo [m, b]. 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 f (m) = 0 o, como ya sabemos, es mejor utilizar la condición f (m) < ✏, 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.5. Una aplicación interesante: Raíces de ecuaciones algebraicas cualquiera 49


Curso de Computación Científica, Versión 2019-09-19

5.6 Ejercicios

1. Escribir un programa que calcule el factorial de un número n entero y positivo. Es decir, calculen:
n
Y
n! = k para n 0
k=1

2. Crear una lista con 10 números enteros con valores distintos y arbitrarios de 0 a 100. Programar 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 funció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. Obtener un valor de ⇡ calculando la suma siguiente para n=200:
n
X ( 1)k+1
4
2k 1
k=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:
n
X 1 3
= ,
i=0
(i + 1)(i + 3) 4

para obtenerla con 5 cifras significativas. Como resultado dar el valor de la suma y el número n de sumandos
sumados.

50 Capítulo 5. Control de flujo


Curso de Computación Científica, Versión 2019-09-19

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,6c)
p = 0,6c + z
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

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:
1
X a
axn =
n=0
1 x

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 monedas 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 ax2 + bx + c = 0 para cualquier valor de a, b y
c comprobando el valor del discriminante = b2 4ac.
16. Una de las formas de medir distancias en el cosmos es utilizar el llamado módulo de distancias, en el que
la diferencia entre las magnitudes aparente mv (proporcional al logaritmo del flujo recibido) y absoluta Mv
(proporcional al logaritmo del flujo recibido si el astro se encontrara a 10 parsec de distancia) de un astro es
mv Mv = 5 log 10 d
+ ad, donde, d es la distancia en parsec y a 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 mv = 3. Sabiendo que este tipo de supernovas tienen
una magnitud absoluta de Mv = -19.3 y que el coeficiente de absorción interestelar es de a = 0,8 ⇥ 10 4 , ¿a
qué distancia se encuentra la galaxia?.
17. Los números narcisistas son aquellos números enteros en que la suma de sus dígitos elevados a la potencia de
su número de cifras es igual a dicho número. Por ejemplo, los números 153 (de 3 cifras) y 1634 (de 4 cifras) son
narcisistas porque cumplen:

153 = 13 + 53 + 33 = 1 + 125 + 27

5.6. Ejercicios 51
Curso de Computación Científica, Versión 2019-09-19

1634 = 14 + 64 + 34 + 44 = 1 + 1296 + 81 + 256


Escribe un programa Python que calcule y genere una lista con todos los números narcisistas entre 1 y 99999.
Imprime por pantalla dicha lista.

52 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 C de tal forma que }(C) = 1 y que para cualquier subconjunto S ⇢ C se tiene que 0  }(S)  1.
Además para cualquier familia Si de subconjuntos de C, disjuntos dos a dos, se verifica que: }(S1 [ S2 [ ... [ Sn ) =
}(S1 ) + }(S2 ) + ... + }(Sn ). 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 (n) 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 distribución
uniforme, en la que todas las caras (de la 1 a la 6) han salido igual número de veces (n/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 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 reconocer 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

53
Curso de Computación Científica, Versión 2019-09-19

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]
6

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=list(range(15)) # obtenemos lista ordenada de numeros del 0 al 14
>>> print(x)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

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


9
>>> 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 distribució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

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


Curso de Computación Científica, Versión 2019-09-19

Si n es pequeño (n = 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 n = 6000, por ejemplo) el resultado para
cada cara se irá aproximando a n/6. Pueden comprobarlo repitiendo la serie de comandos anterior para un número
creciente de veces (n) la desviación decrece al aumentar el número de tiradas.

6.1 Algunas distribuciones de probabilidad: la Distibución Binomial

Los datos obtenidos por la observación y experimentación en muchos campos tales como la ciencia, la ingeniería,
la sociología, las finanzas y varios juegos de azar presentan muy diferentes distribuciones 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: la Binomial, la de Poisson y la Gaussiana. Todas ellas pueden derivarse y
expresarse matemáticamente 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
n) el resultado será de n/2 caras y cruces, aproximadamente. No obstante, si n es pequeño (n = 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
n veces, >cuál es la probabilidad de que obtengamos un resultado en el que nos salgan m caras? (obviamente 0 
m  n). Puede demostrarse que si lanzamos la moneda n veces la probabilidad de obtener m = 0, 1, 2, ..., n veces
cara (o cruz) viene dada por los términos sucesivos del binomio (1/2 + 1/2)n .
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 n elementos, nos podemos preguntar cuántos grupos de m 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:
n!
V (n, m) =
(n m)!
Podemos entender este resultado si pensamos que la primera posición de nuestro grupo puede estar ocupada por uno
cualquiera de los n elementos, la segunda sólo por (n 1) puesto que ya hemos utilizado uno, la tercera por (n 2)
ya que hemos utilizado 2 y así hasta la m-ésima en la que podemos poner uno de los (n (m 1)) que quedan.
Recordemos que n! es el llamado factorial de n 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: V (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 genera-
mos los grupos de m 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í:

V R(n, m) = nm

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


Curso de Computación Científica, Versión 2019-09-19

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á: V R(n, m) = nm = 314 =
4782969.

Permutaciones. El problema ahora es ligeramente diferente y lo que nos preguntamos es de cuántas formas podemos
ordenar los n 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 n elementos tomadas de n en n y por lo tanto lo calculamos como
(recuerden que 0!=1):

P (n) = V (n, n) = n!

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: P (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 m elementos que podemos formar del conjunto de
n 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 cualquiera y deseamos saber
en cuantas de ellas hemos obtenido un resultado concreto. Se calcula utilizando la fórmula:
n!
C(n, m) =
m!(n m)!

esta fórmula puede interpretarse como el número de permutaciones de los n elementos (n!) dividido por el número de
permutaciones de los m elementos seleccionados (m!) multiplicadas por el número de permutaciones de los restantes
n m, ((n m)!).

Nota: Ejemplo. ¿De cuántas formas podemos lanzar una moneda 12 veces y obtener cuatro cruces?. Imaginen que
tenemos 12 monedas de las cuales cuatro son cruces. Además tenemos 12 posiciones donde colocar nuestras monedas.
De cuántas formas podemos colocarlas, sabiendo que no distinguimos unas monedas de otras más que por el hecho de
ser o no ser caras?. 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, C(12, 4) = 4!8!
12!
= 12·11·10·9
4·3·2·1 = 495.

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 p y la de que no lo obtengamos es q (de tal forma que p + q = 1), las
probabilidades de que suceda en 0, 1, 2, ..., n veces de las n veces que lo intentemos, viene dado por los términos
sucesivos del binomio (q + p)n , es decir:
m=n
X
n(n 1) n n(n 1)(n 2) n!
q n + nq n 1
p+ q 2 2
p + qn 2 2
p + ... + pn = q m pn m
2! 3! m=0
m!(n m)!

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

n!
}(m, n) = pm q n m
m = 0, ..., n
m!(n m)!

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


Curso de Computación Científica, Versión 2019-09-19

para n=12 tenemos los diferentes términos:


1 1 12 · 11 1 1
}B (0, 12), }B (1, 12), }B (2, 12), ..., }B (12, 12) = , 12 12 , , ..., 12
212 2 2 · 1 212 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 f denota la frecuencia con la que un suceso ocurre n
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 producció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 memorias 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.

6.2. Ejercicios 57
Curso de Computación Científica, Versión 2019-09-19

58 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 cuando 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 laborioso 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 propiedades 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 = list(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)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)

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


(continues on next page)

59
Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)

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, polinomios 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 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 funciones, 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:

In [9]: x = array([2.0, 4.6, 9.3, 1.2]) # Crear array directamente


In [10]: notas = [ 9.8, 7.8, 9.9, 8.4, 6.7] # Crear lista
In [11]: notas = array(notas) # convertir lista en array

Existen métodos para crear arrays automáticamente:

In [12]: numeros = arange(10.) # Array de 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.]

(continues on next page)

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


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


In [18]: otra_lista = linspace(0,30,8) # Array de 8 números (0 a 30)
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


[ 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, 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'>
(continues on next page)

7.3. Indexado de arrays 61


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)

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

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

In [36]: type(decimales)
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. ]

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

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


Curso de Computación Científica, Versión 2019-09-19

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 insert(). Si en lugar de un
elemento a insertar se da una lista y otro array, añade todos los elementos de la lista (a insert() 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 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])

(continues on next page)

7.5. Operaciones con arrays 63


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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 booleano con el resultado
que corresponda, pero al multiplicar los arrays booleanos con arrays numé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]

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 trabajar 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]
(continues on next page)

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


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


[ 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


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
(continues on next page)

7.6. Arrays multidimensionales 65


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


In [93]: arr0[-1,:] = arr0[-1,:]*10

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

7.7 Ejercicios

1. Crea un array de 100 números aleatorios con valores de -100 a +100 como hicimos en el ejercicio 4 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 10 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.

66 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 Probabilidad, 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 N ⇡
M /mH ⇡ 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 comprimir 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 estadísticos y entre los más conocidos están: un parámetro de
posición, la media, y otro de «amontonamiento», 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 «pregun-
tando» cual es el valor exacto o verdadero de una cierta cantidad. Esta actitud 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.

67
Curso de Computación Científica, Versión 2019-09-19

Supongamos que hemos realizado un conjunto de N medidas de una determinada cantidad x y que las representamos
por {xi } (i = 1, 2, ..., N ). Esperamos que las diferentes medidas realizadas xi , 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 histograma1 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 verdadero de la cantidad
medida, lo que llamamos el valor medio. Comenzaremos por definir la media aritmética (o simplemente media) como:
P
xi
x= i
N
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:

x = (⇧i xi )(1/N ) ,

esta definición resulta útil cuando la incertidumbre en nuestro problema actúa de forma multiplicativa 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 xmed , cumple que n(x < xmed ) = N/2 = n(x > xmed ). 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 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 experimento 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:
P
2 (xi x)2
= i
N
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).

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


Curso de Computación Científica, Versión 2019-09-19

donde tomamos como valor real de la cantidad medida su valor medio. Por cierto, pueden demostrar, sin demasiado
problema, que también puede calcularse como 2 = x2 x2 ; 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 N a (N 1). Nótese que a medida que N
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 suerte que suele llamársela también
distribución normal (ver Apéndice C). 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, y fue ampliamente 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, es decir su histograma, es
la famosa «Campana de Gauss».
Por otro lado, es bastante usual ver a un conjunto de observaciones o resultados de cualquier experimento 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 experimento 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 desviaciones estándar también
es una distribución
p gaussiana. Su media es igual a la media verdadera de toda la población mientras que su desviación
estándar es / n donde n 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 x y desviación
p estándar , es apropiado escribir el resultado final del experimento u observación como: x ± ✏,
donde ✏ = / n, 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 {xi } cada una de ellas con un peso estadístico asociado, con lo
que tendremos finalmente un conjunto de pesos {wi }. En este caso, para calcular la media, que se suele llamar media
ponderada, hacemos:
P
w i xi
x = Pi (8.1)
i wi

Con numpy podemos utilizar average() para calcularla y análogamente, siguiendo el apartado anterior, podemos
calcular su varianza usando:
P
2 wi (xi x)2
= i P (8.2)
i wi

donde la x que debemos utilizar es la ponderada. En el caso de medidas experimentales, una elección habitual para
los pesos es utilizar valores inversamente proporcionales al cuadrado de los errores de cada medida, cuando éstas

8.2. La distribución de datos subyacente 69


Curso de Computación Científica, Versión 2019-09-19

son conocidas o pueden ser estimadas, es decir, wi = 1/✏2i . La varianza asociada a este promedio la calculamos
sustituyendo apropiadamente en la relación anterior.
P
2 (xi x)2 /✏2i
= iP 2
(8.3)
i 1/✏i

8.3 Sesgo y robustez

En palabras llanas nos gustaría que el conjunto de nuestras medidas tuvieran las cualidades siguientes: 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 aproxime 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 errores 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 imposible de obtener. No
obstante, siempre podemos mejorar las observaciones, los instrumentos y los experimentos de tal forma que minimi-
cemos 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 x y varianza 2


, ésta última también a podemos calcular
como 2 = x2 x2 .
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 distribución de datos con sus
correspondientes pesos (wi = 1/ i2 ) 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úmeros) de la misma lista
del problema anterior.
6. Para el ejercicio 2 del Apéndice C sobre los diámetros de las esporas del lycopodium, calcular, usando fun-
ciones 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 d < d d

Que tengan valores superiores a la media más la desviación estándar, es decir d > d + d

Que tengan valores entre d d <d<d+ d

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


Curso de Computación Científica, Versión 2019-09-19

7. En observaciones de dispersión de electrones, en el detector se mide la intensidad relativa del haz a diferentes ra-
dios de curvatura obteniendo los datos de la tabla adjunta. Encontrar 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

8.4. Ejercicios 71
Curso de Computación Científica, Versión 2019-09-19

72 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 especialmente 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 indicado 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
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:

73
Curso de Computación Científica, Versión 2019-09-19

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


In [50]: type(fs)
Out[50]: <class '_io.TextIOWrapper'>

aquí la variable fs es una instancia o llamada al fichero. A partir de ahora cualquier operación que se realice en el
fichero se hace utilizando esta instancia fs y no el nombre del fichero en sí. Introducimos el parámetro enconding
= 'utf8' para que el fichero contenga caracteres unicode y podamos escribir la ñ o tildes sin problemas. Si no
incluimos explícitamente este parámetro, el fichero se creará con caracteres ascii por defecto y tendremos problemas
al usar letras o caracteres especiales ajenos al idioma inglés. Python 3 interpreta un fichero como de perteneciente a
la clase _io.TextIOWrapper, un repositorio de lectura o escritura de texto. 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 línea')
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 para el
«intro» o el «return», es decir para generar una nueva línea:

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


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

De igual manera podemos usar un bucle for para escribir una lista de datos (al ser números no necesitamos usar
carácteres unicode y por lo tanto no usamos encoding):

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.

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
(continues on next page)

74 Capítulo 9. Lectura y escritura de ficheros


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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 ejemplo usamos el método readlines() al fichero para que lea línea a linea y devuelve 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.
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, . . . ).

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.

9.3. Lectura y escritura de ficheros de datos con numpy 75


Curso de Computación Científica, Versión 2019-09-19

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)

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 prin-
cipio 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.
3. Usando la función randint() de numpy.random, generen una lista de 30 números enteros 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.

76 Capítulo 9. Lectura y escritura de ficheros


Curso de Computación Científica, Versión 2019-09-19

4. Calcule en un array los valores que toma la función seno-cociente: sen(✓)/✓ 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 correspondiente.
5. A lo largo de la historia desde que O. Roemer en 1676 midiera por vez primera la velocidad 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.

9.4. Ejercicios 77
Curso de Computación Científica, Versión 2019-09-19

78 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 me-
diante 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ándolos todos juntos. Sin embargo, cuando
trabajamos interactivamente, por ejemplo con la consola ipython podemos activar el modo interactivo para que

79
Curso de Computación Científica, Versión 2019-09-19

cada cambio que se haga en la gráfica 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 modo interactivo, y además se importa el módulo numpy
y todas sus funciones. Si usamos el entorno Spyder de la distribución Anaconda, el modo interactivo funcionará
por defecto, sin ser necesario utilizar las funciones ion() y show().
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 poste-
riormente 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


print(a) = [3]

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

Y esto es justo lo que hicimos con mi_dibujo, = plot(x), para hacer que mi_dibujo contenga 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 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>]

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


Curso de Computación Científica, Versión 2019-09-19

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
podemos borrar toda la figura usando la función clf() o cla(), esta última opción sólo borra lo que hay dibujado
dentro de los ejes y no los ejes en si.
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

Símbolo Color
“b” Azul
“g” Verde
“r” Rojo
“c” Cian
“m” Magenta
“y” Amarillo
“k” Negro
“w” Blanco

Marcas y líneas

81
Curso de Computación Científica, Versión 2019-09-19

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

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]:
[<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.

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


Curso de Computación Científica, Versión 2019-09-19

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 adjunta:

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
markered- Color del borde del símbolo
gecolor o
mec
markered- Ancho del borde del símbolo, float (en número de puntos)
gewidth o
mew
markerfaceco- Color del símbolo
lor 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 lw Ancho de la línea, float (en número de puntos)
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='r')


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

83
Curso de Computación Científica, Versión 2019-09-19

También es posible cambiar las propiedades de la gráfica una vez creada, para ello debemos capturar las instancias
de cada dibujo en una variable y cambiar sus parámetros.

>>> # 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

usando instancias similares poder cambiar prácticamente todas las propiedades de nuestro gráfico sin tener que reha-
cerlo. Por tanto es buena costumbre guardar las instancias en variables cuando trabajemos interactivamente.

10.1 Trabajando con texto dentro del gráfico

Existen 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 gráfica') # 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)

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


Curso de Computación Científica, Versión 2019-09-19

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 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')


Out[37]: <matplotlib.text.Text object at 0x94f172c>

In [38]: ylabel('Amplitud / cm')


Out[38]: <matplotlib.text.Text object at 0x94f20ec>

10.1. Trabajando con texto dentro del gráfico 85


Curso de Computación Científica, Versión 2019-09-19

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. Pue-
des 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 funció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
.....:

>>> 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)
(continues on next page)

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


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)

>>> 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>

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 diferentes 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 (llamados 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:

10.3. Histogramas 87
Curso de Computación Científica, Versión 2019-09-19

>>> # 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,
-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() de-
vuelve 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),

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


Curso de Computación Científica, Versión 2019-09-19

como aparece en el título de la ventana. Podríamos crear 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')

En este ejemplo podemos encontrar que las unidades de los ejes de ordenadas pueden aparecer sobreescritas sobre los
gráficos situados a la izquierda, para que esto no ocurra y conseguir una figura final con una mejor estética podemos
utilizar:

>>> tight_layout()

Con lo que evitaremos el solapamiento entre elementos de gráficas adyacentes.

10.5. Varios gráficos en una misma figura 89


Curso de Computación Científica, Versión 2019-09-19

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 so-
lemos 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 generarlos directamente como hemos hecho hasta ahora, es tan sen-
cillo 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


>>> col1, col2 = loadtxt('datos_2col.txt', unpack=True)

>>> p1, = plot(col1, 'r.') # Primera columna, puntos rojos (r)


>>> p2, = plot(col2, 'b.') # Segunda columna, puntos azules (b)

>>> # 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')
>>> <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>
(continues on next page)

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


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


>>> 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 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])
(continues on next page)

10.7. Datos experimentales con barras de error 91


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)

>>> # 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')

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 decimal, 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')

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


Curso de Computación Científica, Versión 2019-09-19

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 representar 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,
>>> # 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()

10.8. Representación de datos bidimensionales 93


Curso de Computación Científica, Versión 2019-09-19

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)

Las imágenes en color tienen 3 canales RGB. Esto lo podemos ver haciendo:

>>> foto.shape
(334, 501, 3)

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


Curso de Computación Científica, Versión 2019-09-19

donde el 3 indica el número de canales de color. Podemos limitarnos a seleccionar 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 extensió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 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 propiedades 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:

x=a b sen , y=a b cos

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 apropia-
damente 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 apropia-
damente 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. La variación de temperatura de un objeto a temperatura T0 en un ambiente a Ts cambia de la siguiente manera:

kt
T = Ts + (T0 Ts )e

10.9. Guardando las figuras creadas 95


Curso de Computación Científica, Versión 2019-09-19

con t en horas y k un parámetro que depende del objeto. a) Representar gráficamente la variación de la tem-
peratura con el tiempo, partiendo de una T0 = 5 C 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.
4. Representen nuevamente la curva del apartado a) del ejercicio anterior superponiendo además las curvas corres-
pondientes a temperaturas iniciales distintas, de T0 = 5 C y T0 = 15 C. Para T0 = 5 C 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.
5. Con la serie de Gregory-Leibnitz para el cálculo de ⇡ usada anteriormente en el problema 5.5:

n
X ( 1)k+1
4
2k 1
k=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 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.
6. 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 desintegració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 barras 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.
7. El movimiento de un oscilador amortiguado se puede expresar de la siguiente manera:

k!t
x = A0 e cos (!t + )

Siendo A0 la amplitud inicial, ! la fecuencia angular de oscilación y k el factor de amortiguamiento. 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 x = A0 e k!t
y x = A0 e k!t .
8. La curva plana llamada epicicloide, tiene por coordenadas cartesianas (x,y) las siguientes:

a a
x = (a + b) cos b cos( + 1) , y = (a + b) sin b sin( + 1)
b b

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.
9. 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 experimento 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.

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


Curso de Computación Científica, Versión 2019-09-19

10. Representar gráficamente las siguientes funciones:

(x x0 )2
f (x) = a e 2c2

b
g(x) =
(x x 0 ) 2 + b2

usando los valores a = 2.0, x0 = 10.0, c = 5.0 y b = 0.5, en el intervalo de x [-50,+50]. Comprueba cómo afecta
a las gráficas distintos valores de los parámetros c y b.
11. 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:

sen ⇡asen✓
I(✓) = I0 ( )2 =

donde a es el ancho de la rendija, la longitud de onda de la luz, I0 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 I0 = 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.

10.10. Ejercicios 97
Curso de Computación Científica, Versión 2019-09-19

98 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 extensivamente 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 xi con
Pi=n
i = 1, 2, ..., n, el valor más probable de esta cantidad es X si verifica que la i=1 (xi X)2 es mínima.
Es útil comprobar que en este caso el valor más probable coincide con el valor medio del conjunto de observaciones.
En efecto, para comprobarlo no tenemos mas que calcular el mínimo de la función anterior, es decir:
i=n
! i=n P
d X X xi
2
(xi X) =0) 2 (xi X) = 0 ) X = =x
dX i=1 i=1
n

Nótese que cuando esta función es mínima lo es también la varianza, 2


del conjunto de observaciones xi .

11.2 Aplicación al ajuste de funciones a datos experimentales

El problema al que vamos aplicar este principio de mínimos cuadrados es el siguiente:


Supongamos que yi son n medidas de una cantidad y (o una combinación de ellas) que se corresponde con otras n
medidas xi de otra cantidad x. Podemos pensar que existe una relación entre ellas, por ejemplo y = f (x), y queremos
determinar precisamente cuál es esta función f que relaciona ambas cantidades.

99
Curso de Computación Científica, Versión 2019-09-19

Aplicando el principio de mínimos cuadrados podemos decir que la función más probable que relaciona ambas canti-
dades será aquella que haga que la función:
i=n
!
X 2
(f (xi ) yi ) sea mínima.
i=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 x con y es: y = f (x) = ax + b, por lo tanto la recta más probable que
relaciona el conjunto de observaciones xi con el de yi será la que minimice la función:
i=n
!
X 2
M= (axi + b yi )
i=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
i=n
!
@M X
=0=2 xi (axi + b yi )
@a i=1

i=n
!
@M X
=0=2 (axi + b yi )
@b i=1

es decir,
X X X
a x2i + b xi = x i yi
X X
a xi + bn = yi

y resolviendo este sistema lineal de dos ecuaciones con dos incógnitas (a y b) nos queda:
P P P
n x i yi x yi
a= P 2 Pi
n xi ( xi ) 2

P P P P
yi x2i x x i yi
b= P Pi
n x2i ( xi ) 2
con lo que queda resuelto el problema.
Conviene fijarse en algunas características de este problema como las siguientes:
P P
1. Si b = 0 entonces y = ax, donde a = xi yi / x2i .
2. Pueden comprobar que la recta obtenida pasa por el punto (x, y).
3. Se definen los llamados residuos del ajuste como di = axi + b yi .
P 2
4. Pueden comprobar que la varianza del conjunto de di será 2
= di /(n 2), donde n 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 cuadrados.
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:
P 2
( (x x)(y y))
r2 = P P
(x x)2 (y y)2

100 Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Versión 2019-09-19

11.2.2 Aplicación al caso de polinomios de cualquier orden


Pk=m
En el caso de ajustes a polinomios de cualquier orden (m), es decir k=0 ak xk , el método funciona de igual modo.
Lo único que sucede es que, al tener que determinar m + 1 parámetros, el sistema de ecuaciones lineales que aparece
es precisamente de orden m + 1 complicando algebraicamente la solución pero no conceptualmente. Veámoslo a título
informativo.
Supongamos que tenemos una serie de medidas (x1 , y1 ), (x2 , y2 ), (x3 , y3 ), . . . , (xn , yn ), siendo x la variable indepen-
diente e y la dependiente. Para un modelo f (x) de estos datos, hay un error r para cada medida de r1 = y1 f (x1 ), r2 =
y2 f (x2 ), . . . , rn = yn f (xn ). Según el método de mínimos cuadrados, la mejor función de ajuste f(x) es aquella
en la que
n
X n
X
R = d21 + d22 + · · · + d2n = d2i = |yi f (xi )|2
i=1 i=1

es minimo. La forma canónica de este problema es

Xa = Y

en la que X es una matriz MxN para M medidas experimentales y N grados de libertad y el objetivo es minimizar
|Ax y|. En el caso del ajuste a un polinomio de grado N, el modelo sería de la forma

y(x) = aN xN + · · · + a2 x2 + a1 x + a0

para el que habría que escojer la combinación de parámetros ai que mejor se ajusten a los datos. Así, la matriz A,
llamada matriz de Vandermonde tiene esta forma:
2 N 3
x1 · · · x21 x1 1
6 xN 2 7
6 2 · · · x2 x2 1 7
6 .. 7
4 . 5
xN
M ··· x2M xM 1

en ella, cada fila corresponde a una medida experimental Ax = yi .

11.3 Ajuste a polinomios con Python

La función polyfit() de numpy permite ajuste de datos experimentales a polinomios de cualquier 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 ])

(continues on next page)

11.3. Ajuste a polinomios con Python 101


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


# 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 f (x) = ax + b
de nuestros datos será:

y(x) = p0 x + p1 = 2,7x + 9,94

Ahora podemos dibujar los datos experimentales y la recta ajustada:

# 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

102 Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Versión 2019-09-19

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
Pn relativa. Nos interesa especialmente
el residuo del ajuste, que es la suma cuadrática de todos los resíduos i=1 |yi f (xi )|2 . Para el ejemplo anterior
tendríamos lo siguiente:

# Ajuste a una recta, con salida completa


resultado = polyfit(x, y, 1, full=True)

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

b
y=
x+a
se puede linealizar a y 0 (x) = a0 x + b0 haciendo el cambio

1 a0 1
y0 = a= b= .
y b0 a0

ahora basta con ajustar la recta y 0 (x) = a0 x + b0 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.

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) inclu-
yendo 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

11.4. Ajuste de funciones no lineales 103


Curso de Computación Científica, Versión 2019-09-19

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)
# 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


(continues on next page)

104 Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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])

# 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'))

11.4. Ajuste de funciones no lineales 105


Curso de Computación Científica, Versión 2019-09-19

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 simplificada y más práctica de usar leastsq() para ajuste de
curvas.

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 (buscar archivo de datos en el Aula Virtual) posee medidas de presión
y temperatura para 10 mol de hidrógeno, que se someten a distintas temperaturas a volumen constante. 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 (buscar archivo de datos en el Aula Virtual) posee medidas de presión y
volumen 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 comporta idealmente y por tanto que se verifica

106 Capítulo 11. Ajuste de datos experimentales: el método de mínimos cuadrados


Curso de Computación Científica, Versión 2019-09-19

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 largo de 50 segundos; las
medidas de posición y tiempo se encuentran en el fichero medidas_movimiento_acelerado.txt (buscar archivo
de datos en el Aula Virtual). Hacer un ajuste a los datos de un movimiento uniformemente 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 N (t) = N0 e kt , donde N (t) es
la cantidad de material que queda en un tiempo t, N0 la cantidad original (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 N (T ) = N0 /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 (buscar archivo de datos
en el Aula Virtual). 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 inde-
pendientes a lo largo de 60 días. Esas medidas están en el fichero medidas_decaimiento_yodo131b.txt (buscar
archivo de datos en el Aula Virtual), donde cada fila corresponde a cada una de las 5 medidas realizadas dia-
riamente 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 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 L0 a temperatura inicial T0 , que es sometido posteriomente a una temperatura T
sufre una dilatación o contracción dada aproximadamente por L = ↵ T ) donde T es la diferencia de tem-
peraturas y ↵ el coeficiente de dilatación característico del metal. En un laboratorio se mide la dilatación que ex-
perimentan cuatro varillas de metal de distinto material de longitud inicial L0 = 10cm al ir aumentando progre-
sivamente su temperatura 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.
8. La ley de Hubble establece que hay una relación lineal entre la velocidad a la que se alejan las galaxias de
nosotros y la distancia a la que se encuentran, de la forma: v = Hd, donde v es la velocidad de recesión de la
galaxia, d es su distancia y H es la llamada constante de Hubble. En un proyecto para determinar la constante de
Hubble de expansión del Universo se miden las distancias y velocidades de recesión de un conjunto de galaxias.
Los resultados obtenidos se encuentran en el fichero datoshubble.txt (buscar archivo de datos en el Aula Virtual).
Se pide que escriban un programa en el que:
Lean el fichero de datos observacionales y los representen gráficamente, con sus errores en ambos ejes,
en una figura. Tengan cuidado con las unidades: la distancia está en Mpc (megaparsec) y la velocidad en
km/s; 1 parsec = 3,0857 ⇥ 1016 m. Rotulen la figura y los ejes apropiadamente.
Realicen un ajuste de los datos observacionales a la ley de Hubble y obtengan el valor de la constante de
Hubble, imprimiendo su valor por pantalla e indicando las unidades correspondientes. Dibujen el ajuste
obtenido en la misma figura y con un color diferente al de los datos.
Observen que podemos obtener una estimación de la edad del Universo (tiempo transcurrido desde el Big
Bang) calculando la inversa de a constante de Hubble, es decir: ⌧ = 1/H. Calculen tal edad (en años) a
partir del valor que hayan obtenido de H y muéstrenla también por pantalla.

11.5. Ejercicios 107


Curso de Computación Científica, Versión 2019-09-19

108 Capí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 carre-
ra. scipy es una colección de paquetes de algoritmos 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
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) (haciendo 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 trabajar 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

109
Curso de Computación Científica, Versión 2019-09-19

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ángulos, 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 f (x) definida en un intervalo [a,b] , la definición moderna de integral:
Z b
I(f ) = f (x)dx
a

se basa en el concepto de las sumas de Riemann de la forma:


n
X
Rn = (xi+1 xi )f (⇠i )
i=1

donde, a = x1 < x2 < ... < xn < xn+1 = b y ⇠ 2 [xi , xi+1 ], i = 1, ..., n.
Supongamos que hn = máx (xi+1 xi ) : i = 1, ..., n 1. Si para cualquier xi tal que hn ! 0 y cualquiera que
sea ⇠i tenemos que el lı́mn!1 Rn = R finito, entonces se dice que f es integrable en el sentido de Riemann en el
intervalo [a, b] y que el valor de la integral es R.
Como pueden intuir, esta definición enseguida sugiere una forma sencilla de calcular aproximadamente la integral.
Sólo tenemos que usar una suma de Riemann con n suficientemente grande para alcanzar la precisión requerida en
el valor de la integral. Esta idea funciona, pero debemos elegir los valores de xi y ⇠i 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.

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 I(f ) por una regla de cuadratura de n puntos que tiene
la forma de:
n
X
Qn (f ) = wi f (xi ) , a  x1 < x2 < ... < xn  b.
i=1

A los puntos xi se les llama nodos de abcisas o nodos simplemente y a los factores wi se les llama pesos o coeficientes.
Una cuadratura se llama abierta si x 2 (a, b) y cerrada si x 2 [a, b]. 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.

110 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Versión 2019-09-19

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 separados 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:

b a
xi = a + i , i = 1, ..., n.
n+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:
n
X1 xi + xi+1
M (f ) = h f( ) h = xi+1 xi 8i.
i=1
2
Regla del trapecio: Consiste en evaluar la función en ambos extremos del intervalo y sustituir 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:
n
X1 f (xi ) + f (xi+1 )
T (f ) = h h = xi+1 xi 8i.
i=1
2
Regla de Simpson: Se evalúa la función en los extremos del intervalo y en el punto medio del mismo sustitu-
yendo 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:
n 1✓ ◆
hX xi + xi+1
S(f ) = f (xi ) + 4 · f ( ) + f (xi+1 ) h = xi+1 xi 8i.
6 i=1 2

12.1.4 La cuadratura en Scipy de Python

El subpaquete integrate del paquete scipy de Python ofrece varias herramientas de integració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.

(continues on next page)

12.1. La integración o cuadratura numérica 111


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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.

Existen por ejemplo implementaciones del método del trapecio o el método de Simpson, ambos 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(x) 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

112 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Versión 2019-09-19

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ítica también lo podemos
hacer utilizando la función de Python de uso general más eficiente quad(func, a, b), que integra por cuadratura
de Clenshaw-Curtis1 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))
1.1308638867425838e-15

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 +1 o 1 usando los string +Inf o -Inf; por ejemplo
R2 2
podemos calcular la integral (ver figura) 2 2exp x5 dx,
>>> 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 1 hasta +1 obtendremos toda el área bajo la curva definida en el
integrando:
1 Para detalles, ver por ejemplo http://en.wikipedia.org/wiki/Clenshaw-Curtis_quadrature

12.1. La integración o cuadratura numérica 113


Curso de Computación Científica, Versión 2019-09-19

# integración entre -infinito y +infinito


>>> int2, err2 = integrate.quad(func1, -Inf, +Inf)
# y el resultado obtenido es:
>>> print(int2,err2)
(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


R⇡ args como una tupla de parámetros
(ver la ayuda de quad()). Por ejemplo, calculemos la integral siguiente: 0 (a + bsen(x))dx 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:

>>> # 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]])

(continues on next page)

114 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


>>> 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'>
>>> # 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

12.3. Operaciones básicas con matrices 115


Curso de Computación Científica, Versión 2019-09-19

usando el método A.I si A es una matriz. Por ejemplo, consideremos


2 3
1 3 5
A =4 2 5 1 5
2 3 8
entonces:
2 3 2 3
37 9 22 1,48 0,36 0,88
1 4
A 1
= 14 2 9 5 = 4 0,56 0,08 0,36 5 .
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.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:
x + 3y + 5z = 10
2x + 5y + z = 8
2x + 3y + 8z = 3

Podemos encontrar la solución usando la matriz inversa:


2 3 2 3 12 3 2 3 2 3
x 1 3 5 10 232 9,28
4 y 5=4 2 5 1 5 4 8 5= 1 4 129 5 = 4 5,16 5 .
25
z 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. Hay que llevar especial cuidado en separar las líneas con “;”:
>>> 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]])

(continues on next page)

116 Capítulo 12. Otras aplicaciones de Cálculo Numérico


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


>>> # 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 aij son los elementos de la matriz A y Mij = |Aij | 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:
X i+j
|A| = ( 1) aij Mij .
j

Con Scipy el determinante se puede calcular con linalg.det. Por ejemplo, el determinante de la matriz A
2 3
1 3 5
A =4 2 5 1 5
2 3 8
es
5 1 2 1 2 5
|A| = 1 3 +5
3 8 2 8 2 3
= 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

Z 1 p Z 2⇡
3 x
2⇡ x 1+ 9x4 dx e sin (10x) dx
0 0

2. Comprobar las siguientes integrales calculándolas numéricamente:

Z 1 Z 4 Z 2⇡
x2 dx 1 dx 2⇡
e dx = 0,746 = tan (4) = 1,3258 = p = 3,627
0 0 1 + x2 0 2 + cos x 3

3. Calcular numéricamente el área más pequeña comprendida entre un círculo x2 + y 2 = 25 y la recta x=3.
4. Escribir un programa en el que dibujen la función 1lnxx 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.

12.5. Ejercicios 117


Curso de Computación Científica, Versión 2019-09-19

5. Crear una función que calcule numéricamente la siguiente integral admitiendo parámetros de entrada m y n:

Z 1
xm xn m+1
⇡ dx = ln
0 ln x n+1

6. Las leyes de Kirchhoff de circuitos, indican que la suma de los voltajes en cada malla deP un circuito
P es igual
a la suma de las resistencias multiplicadas por la corriente que circula por ellas, es decir V =P RI, que
no
P es más que la ley de Ohm generalizada y que las corrientes en los nodos se conserva, es decir, Ientran =
Isalen . Utilizando las leyes de Kirchhoff crear un sistema de ecuaciones para el siguinte circuito y resolverlo
numéricamente para conocer cada una de las corrientes que circulan por él.

7. Resolver el sistema AX=B donde:

2 3 2 3
1 3 5 7 1
6 2 1 3 5 7 6 2 7
6
A =4 7 y B =6 7
0 0 2 5 5 4 3 5
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 ⌦.

118 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.
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.

119
Curso de Computación Científica, Versión 2019-09-19

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 aprender 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 ac-
tividad. Aunque la instalación de Python ya viene con gran cantidad de funcionalidades 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 de 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 ordenador (necesario para
instalar), por lo que te pedirá la contraseña de administrador e instalará los 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.
Finalmente, también podemos descargar la última versión diponible de Python 3 de la distribución Anaconda, que es
la que recomendamos utilizar con los sistemas operativos Linux y Mac como se puede ver a continuación.

13.3.2 Python en Windows o Mac

Tanto en Windows como en Mac (también en Linux) es posible instalar Python y luego instalar los módulos científicos
necesarios, aunque hay varios proyectos o distribuciones 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 3.6 (o la última versión de
Python 3 disponible) 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.6, y aunque podemos emplear
cualquiera y no hay gran diferencia entre ambas, recomendamos usar Python 3.6, pues es la más reciente y en la que
se está desarrollando la mayor parte del nuevo software.

120 Capítulo 13. Apéndice A: Recursos informáticos para el curso


Curso de Computación Científica, Versión 2019-09-19

13.4 Editores de texto

Además del uso de Python interactivamente por terminal de comandos, también necesitaremos hacer programas ejecu-
tables, 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 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 funcional.
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 edició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. Viene incluido por defecto en la distribución Anaconda.
Si usas Linux, probablemente ya tengas instalado alguno de estos editores, si no, utiliza el administrador de paquetes
de tu distribución. También puedes instalar programas usando la terminal de comandos de Linux:

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 TextE-
dit.app que se puede utilizar para editar código, aunque es muy limitado. Recomendamos 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 la distribución Anaconda.
TextWrangler. Editor de texto sencillo de caracter general. Ahora ha pasado a llamarse BBEdit. Usar la versión
gratuita.

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 programas, 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 la distribución 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 conviene tener a mano
algunas guías:

13.4. Editores de texto 121


Curso de Computación Científica, Versión 2019-09-19

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 3, Andrés Marzal, Pedro García Sevilla e Isabel Gracia, 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.
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.org/

122 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 operativo 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. Actualmente, 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 habitualmente 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 para 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.
La barra horizontal inferior es la barra de tareas y aplicaciones, desde donde se accede a los programas 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 nave-
gador Firefox, que se encuentra en la categoría Internet y también un editor de texto (no procesador de textos gráfico)

123
Curso de Computación Científica, Versión 2019-09-19

Figura 1: Escritorio de Bardinux 3.2.

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 cual-
quier 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.
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:~$

124 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Versión 2019-09-19

Figura 2: Pantalla de la teminal de comandos Konsole.

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 3: Listado del contenido de un directorio en la terminal de comandos usando ls.

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 (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 prueba.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:

14.2. Trabajando con la consola de Linux. Directorios y Ficheros. 125


Curso de Computación Científica, Versión 2019-09-19

Figura 4: Listado detallado del contenido de un directorio en la terminal de comandos usando ls -l.

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 estamos 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. Igual-
mente, como el símbolo «~» es una abreviatura de nuestro home, podemos ir a él estemos donde estemos escribiendo
cd ~ o incluso cd solamente.
Linux tiene una estructura de directorio cuyo raíz o directorio base es */* y en él están otros directorios 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 ficheros 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 segundo 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

126 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Versión 2019-09-19

fichero prueba.txt, para Linux en realidad se llama /home/japp/prueba.txt y al «moverlo» realmente lo renombramos a
/home/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 tie-
nes 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>:

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 únicamente 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. Finalmente 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 normales. 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. Recuerda que puedes ver más opciones de los comando con la opción --help.

14.4. Caracteres comodín 127


Curso de Computación Científica, Versión 2019-09-19

Como curiosidad útil, podemos encadenar varios comandos con una «tubería» o «pipeline» usando el carácter «|»
entre comandos. Por ejemplo, si queremos ver las diez primeras líneas del directorio /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:

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.

128 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Versión 2019-09-19

Por defecto, los ficheros se crean con permisos -rw-r–r–, y estos se pueden modificar con el comando 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:

# 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 coman-
do. 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 ocasio-
nes los ficheros pueden ser muchos o muy grandes. Se puede comprimir un fichero con el comando gzip y descompri-
mirlo 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

14.7. Empaquetando y comprimiendo ficheros 129


Curso de Computación Científica, Versión 2019-09-19

Fíjate que gzip sólo comprime ficheros sueltos. Si queremos comprimir varios ficheros o un directorio, 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 adecuadas:

# 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

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 empiecen 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 modificarlo.
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 palabra 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 ordenador. Muestra sólo
los 10 últimos.

130 Capítulo 14. Apéndice B: El sistema operativo GNU/Linux


Curso de Computación Científica, Versión 2019-09-19

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 coman-
dos_[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 131


Curso de Computación Científica, Versión 2019-09-19

132 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 1 y +1, 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ársela también distribución normal. Aun-
que 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 (x x)2
}G (x, x, ) = p exp[ ]
2⇡ 2 2
y al ser una función continua consideramos esta función como una densidad de probabilidad; es decir que la probabi-
lidad de que una observación se encuentre entre x y x + dx será }G (x, x, )dx. Nótese que x 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:
R1
} (x, x, )dx = 1; esto correspondería a la probabilidad de obtener una observación cualquiera,
1 G

la
R x2probabilidad de que una observación esté comprendida en el intervalo [x1 , x2 ] se calcularía haciendo
x1
}G (x, x, ) dx,
la función es simétrica respecto de x, la media,

133
Curso de Computación Científica, Versión 2019-09-19

1/2
}G (x ± ) = e
p
2⇡
, donde es la desviación estándar,
la probabilidad de que una observación caiga dentro del rango x ± es de 0.6827 y dentro de x ± 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 (n) datos, se puede representar por una distribución normal. Si es así, deberíamos ser capaces de
encontrar los parámetros de la distribución normal (media y distribución estándar de la muestra) que ajustan mejor al
conjunto de datos. Estos parámetros encontrados 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, aunque si n 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 (n), 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 n 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 también una distribución
p gaussiana.
Su media es igual a la media verdadera de toda la población mientras que su desviación estándar es / n donde n 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
p subyacente es una distribución normal
de media x es apropiado escribir la media encontrada como: x ± / n, donde lo escrito detrás del símbolop ± es el
llamado error estándar de la media. De forma similar, el error estándar
p de la desviación estándar es / 2n, de forma
que la desviación estándar la podemos escribir como: (1 ± 1/ 2n).

15.3 La media pesada y su error estándar

Supongamos que tenemos una muestra de n medidas obtenidas de un experimento u observación de cualquier fenó-
meno físico {xi } cuya distribución subyacente es una distribución normal. Además, supongamos que algunas medidas
se han obtenido con mejores precisiones { i } que otras. Puede mostrarse (Gauss fue quien primero lo demostró) que
la media ponderada con los pesos wi = 1/ i2 es el valor más probable de la medida que buscamos (de la distribución
subyacente).
Es decir, como se dice en [6.1]:
P 2
xi / i
x = Pi 2
(15.1)
i 1/ i

y de [6.2] la desviación estándar de la distribución alrededor de esta media será:


P
2 (xi x)2 / i2
= iP 2
(15.2)
i 1/ i

y de la sección anterior podemos concluir que el error asociado al valor medio será:
sP
i (xi Px)2 / i2
✏= (15.3)
(n 1) i 1/ i2

Démonos cuenta que si todas las medidas están tomadas con igual precisión esta definición se reduce a:
sP
i (xi x)2
✏= (15.4)
n(n 1)

134 Capítulo 15. Apéndice C: La distribución Gaussiana


Curso de Computación Científica, Versión 2019-09-19

15.4 Consistencia interna y externa de un conjunto de medidas

Acabamos de ver que si el peso wi asignado a una medida xi es igual (o proporcional) a 1/✏2i , donde ✏i es el error
estándar de xi , entonces el error estándar de la media pesada x 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 3 de esta serie
hemos definido como calcular la propagación de errores en una función cualquiera:
" ✓ ◆2 #
X @x
2 2
x = i (15.5)
i
@xi

En este caso si lo aplicamos a la definición de x podemos llegar a que el resultado final es:

2 1
x =P 2 (15.6)
i (1/ i)

Las expresiones (15.3) (error externo, ✏ext ) y (15.6) (error interno, ✏int ) de la media son muy diferentes 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á:
P 1/2
✏ext i (xi x)2 /✏2i
Z= = (15.7)
✏int (n 1)
y debería ser igual a 1. Para muestras de medidas tomadas de una población normal infinita puede p demostrarse que
✏int y ✏ext deben coincidir o, mejor dicho, siendo estrictos Z debe ser 1 con un error estándar 1/ 2(n 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 x ± max(✏int , ✏ext ).
En la práctica, generalmente encontrarán que esto no es así y que Z no coincidirá con la unidad. Pero esto no debería
sorprenderles ya que, por un lado depende fuertemente de los valores de ✏i ; algunos de ellos pueden ser considerable-
mente
P imprecisos o inexactos. Por otro lado, Z también depende de lo que se separan las medidas de su valor medio
( i (xi x)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 sistemáticos presentes
en el conjunto de observaciones. Si Z 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 errores sistemáticos que se 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 Z ⌧ 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 Docente. OA - UVEG

15.4. Consistencia interna y externa de un conjunto de medidas 135


Curso de Computación Científica, Versión 2019-09-19

15.6 Ejercicios

1. El control de calidad de una fábrica que produce bombillas eléctricas comprueba 1000, elegidas al azar, cada
mes. Encuentran que el número de horas de encendido de las bombillas sigue una distribución normal con una
vida media de 950 horas de encendido y una desviació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) nº de esporas


14 1
15 1
16 8
17 24
18 48
19 58
20 35
21 16
22 8
23 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 distribución nor-
mal.
1. Los ficheros dssn2002.txt y dssn2007.txt contienen las observaciones del índice Im 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 Im en toda la superficie visible solar, 4) el Im solo en
el hemisferio norte visible y 5) el Im 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 última fila obtengan
los valores para todo el año.
2. 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.

136 Capítulo 15. Apéndice C: La distribución Gaussiana


CAPÍTULO 16

Apéndice D: Cálculo Simbólico

En el capítulo 12 vimos cómo es posible usar técnicas numéricas para resolver problemas matemáticos complejos,
como integrales, sistemas de ecuaciones, etc. En Física e ingeniería es prácticamente 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 matemáticos de manera analítica necesariamente y en ocasiones, pueden ser
problemas complejos. Por ejemplo, es posible que para obtener la primitiva de cierta integral recurramos a técnicas de
substitució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 el módulo
Sympy. Sympy es una librería de cálculo simbólico que permite resolver analíticamente múltiples problemas mate-
má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 Integer, 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

También existen algunas constantes especiales, como el número e o ⇡, sin 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:

137
Curso de Computación Científica, Versión 2019-09-19

>>> 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

>>> ((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
-
(continues on next page)

138 Capítulo 16. Apéndice D: Cálculo Simbólico


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


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):

>>> z = Symbol('z')

>>> 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.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.3. Cálculo de límites 139


Curso de Computación Científica, Versión 2019-09-19

16.4 Cálculo de derivadas

La función de Sympy para calcular la derivada de cualquier función es diff(func, var). Veamos algunos ejem-
plos:

>>> 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)

>>> 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

140 Capítulo 16. Apéndice D: Cálculo Simbólico


Curso de Computación Científica, Versión 2019-09-19

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)
>>> 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ácilmente:

>>> # 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}

16.6. Integración 141


Curso de Computación Científica, Versión 2019-09-19

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]

>>> # 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.]])

142 Capítulo 16. Apéndice D: Cálculo Simbólico


Curso de Computación Científica, Versión 2019-09-19

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]

>>> 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] ])
(continues on next page)

16.7. Ecuaciones algebraicas y álgebra lineal 143


Curso de Computación Científica, Versión 2019-09-19

(proviene de la página anterior)


>>> # 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

1. Calcular la derivada de x3 arccos 2


x .
2. Calcular la derivada segunda de e x
log (x).
3. Calcular los diez primeros elementos de la serie:
X 1
.
n=0
(1 + n)5/2

4. Calcular la siguiente integral:


Z
cos (x)
y(x) = dx.
sin (x)

Evaluar el resultado para x=0.5.


5. Calcular la integral siguiente y evaluar su valor en x=1.5:
Z
1
y(x) = dx
1 + ex

6. Comprobar las siguientes integrales definidas:


Z 1 Z 1 Z 1
1 p 1 x 1
q dx = ⇡ x log (1 + x) dx = e sin (x) dx =
0 log 1 0 4 0 2
x

144 Capítulo 16. Apéndice D: Cálculo Simbólico


CAPÍTULO 17

Historial de cambios

17.1 Revisión 3.5 - septiembre 2019

Añadido el problema 17 en el capítulo 5.

17.2 Revisión 3.4 - Septiembre 2018

Revisión general de erratas y tipográficos.

17.3 Revisión 3.3 - Septiembre 2017

Revisión general de erratas y tipográficos.


Actualización de Apéndice sobre Recursos Informáticos.
Se revisa el texto y la sintaxis de los ejemplos para utilizar Python 3.

17.4 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.5 Revisión 3.1 - Septiembre 2015

Error en ejercicio control de flujo. Suma = 3/4 en lugar de 1/2.

145
Curso de Computación Científica, Versión 2019-09-19

17.6 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.
Se añade César Esteban en la lista de autores.

17.7 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.

146 Capítulo 17. Historial de cambios

También podría gustarte