0% encontró este documento útil (0 votos)
24 vistas209 páginas

Analisis Datos

Cargado por

LuisCalderón
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
24 vistas209 páginas

Analisis Datos

Cargado por

LuisCalderón
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 209

Cursos de Formación en Informática - CFI

Visualización de datos con Python

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-CompartirIgual 4.0 Internacional
Cursos de Formación en Informática - CFI

Análisis de datos con Python

Tabla de contenidos

Sobre este Curso

1.1 Objetivos del Curso

1.2 Instalación de Anaconda

1.3 El entorno de desarrollo Jupyter Notebook

1.4 Práctica inicial. Toma de contacto

Elementos básicos del lenguaje

2.1 Elementos básicos del lenguaje

2.2 Las funciones en Python (I)

2.3 Los diccionarios de Python

2.4 De inición por comprensión

2.5 Las funciones en Python (II)

2.6 Ejercicios

Módulo NumPy - Numerical Python

3.1 Importar módulos

3.2 Los arrays de NumPy

3.3 Acceso y recorrido de arrays

3.4 Operaciones vectorizadas

3.5 Ejemplo 1

3.6 Ejemplo 2

3.7 Ejercicios

Series y DataFrames en Pandas

4.1 Las Series y DataFrames de Pandas

4.2 Acceso y actualización de los datos

4.3 Operaciones con Series y Dataframes

Importar y Exportar datos

5.1 Importar y exportar icheros

5.2 Tratamento de icheros en formato HTML

5.3 Datos en Formato JSON


5.4 Acceso a Bases de datos

5.5 Ejercicios

Limpieza y Procesamiento de datos

6.1 Combinar DataFrames

6.2 Preparación y limpieza de los datos

6.3 Grupos y operaciones de agregación

6.4 Ejercicios
Sección 1.1

Sobre este curso

Objetivos del curso

Python es un lenguaje de programación ampliamente utilizado en el contexto cientí co debido a que


dispone de un gran número de librerías que incluyen herramientas para la preparación de los datos
(carga, manipulación, limpieza y procesamiento), su representación grá ca y su posterior análisis
estadístico, ya sea descriptivo como predictivo.

Librerías básicas para el tratamiento de datos

Los contenidos que se abordan en este curso permiten introducir al alumno en la computación cientí ca
en Python. En particular se abordarán aquellas partes del lenguaje y aquellas librerías que resultan
necesarias para resolver el amplio conjunto de problemas relacionados con el cálculo numérico y el
análisis de datos. Los objetivos son:

Introducir el entorno de desarrollo Jupyter Notebook y sus principales características.


Presentar la librería básica para la realización de cálculo cientí co NumPy. Numpy es una librería
que permite trabajar de forma muy e ciente con grandes volúmenes de datos. Para ello utiliza
matrices de múltiples dimensiones, garantizando un uso de memoria y tiempo de ejecución
bajos. Entre los contenidos se mostrará cómo crear arrays multidimensionales y cómo se
procesan de forma e ciente. Se introducen las operaciones vectorizadas como un mecanismo de
cómputo e ciente para este tipo de estructuras. Funciones universales y estadísticas. Gestión de
entrada/salida.
Presentar la librería Pandas y las estructuras de datos Series y DataFrames. Dar a conocer las
características y operaciones básicas asociadas a estas estructuras. Operaciones de limpieza y
transformación de datos. Tratamiento de datos perdidos. Combinación de dataframes con
operaciones merge y join. Estadística descriptiva en Pandas. Grupos y operaciones de
agregación. Carga y almacenamiento de datos.

En cada tema del curso se presentan una serie de conceptos junto con ejemplos prácticos relacionados
con los conceptos explicados. Al nal de cada tema encontrarás ejercicios para entregar y que permitirán
evaluar el uso correcto de los conceptos desarrollados a lo largo del curso. Estos ejercicios son
obligatorios y deberás enviarlos a través de la herramienta de entrega de ejercicios del campus.

¿A quien va dirigido?

Python resulta accesible a un público con un per l no tan tecnológico como nos podemos imaginar. Este
curso va dirigido a todos aquellos estudiantes y profesionales que suelan trabajar con grandes
cantidades de datos y que necesiten extraer información interesante de los mismos.

Requisitos para seguir el curso

Es necesario tener unos conocimientos mínimos del lenguaje de programación Python. Es recomendable
haber realizado el curso CFI de Introducción a la programación en Python antes de realizar este curso.
Sección 1.2

Instalando las herramientas necesarias

Para poder ejecutar los ejemplos contenidos en este curso, es necesario tener accesible un intérprete de
Python 3.X y las librerías de cálculo cientí co Numpy, Pandas y Matplotlib. Lo más sencillo es usar
Anaconda, que proporciona una distribución libre de los paquetes anteriormente mencionados. Incluye
Python 3.X. y multitud de utilidades. Anaconda es una plataforma para análisis de datos en Python y
puede descargarse de su sitio web https://www.anaconda.com/products/individual.

A continuación explicamos paso a paso cómo realizar la instalación de Anaconda. El tutorial está hecho
para Windows, pero Anaconda está disponible también para Mac OS y Linux.

Anaconda

Desde Windows, Linux o Mac lo más sencillo es instalar una distribución de Python gratuita llamada
Anaconda.

Como hemos comentado anteriormente, Anaconda es una distribución de Python que recopila muchas
de las bibliotecas necesarias en el ámbito de la computación cientí ca. Esta distribución incluye multitud
de utilidades que nos facilitarán el trabajo y es ideal para empezar.

Para instalarla, sólo tienes que descargar la versión de Anaconda para tu sistema operativo desde su
página web. Se te abrirá una ventana emergente donde te pedirá tu dirección de email, pero si no quieres
darla, cierra dicha ventana y podrás descargarlo sin problema.

Instalación de Python con Anaconda

Paso 1: Descargar el instalador de Anaconda de acuerdo al Sistema Operativo de su equipo.


Paso 2: Una vez descargado el sofware, procedemos a instalarlo. Haga doble clic en el instalador para
iniciar.

Si encuentra algún problema durante la instalación, deshabilite temporalmente su software antivirus


durante la instalación, luego vuelva a habilitarlo después de que nalice la instalación. Si se ha instalado
para todos los usuarios, desinstale Anaconda y vuelva a instalarlo solo para su usuario y vuelva a
intentarlo.

Paso 3: Haga clic en Siguiente. Lea los términos de la licencia y haga clic en "Acepto".

Paso 4: Seleccione una instalación para "Just Me" y haga clic en "Next".

Paso 5: Seleccione la carpeta de destino que propone el instalador y haga clic en el botón Siguiente.
Paso 5: Recomendamos no agregar Anaconda a la variable de entorno PATH, ya que esto puede interferir
con otro software. Seleccionar Anaconda como su Python predeterminado.

Paso 6: Una vez completada la instalación, verifíquela abriendo Anaconda Navigator, un programa que
se incluye con Anaconda: desde el menú Inicio de Windows, seleccione el acceso directo Anaconda
Navigator.
Si se abre Navigator, ha instalado Anaconda correctamente.

Si su sistema operativo es Mac OS, busque Anaconda Navigator entre sus aplicaciones.

Entorno de desarrollo Jupyter notebook

Anaconda incluye varios entornos de desarrollo. Entre ellos tenemos Jupyter notebook y Spyder.
Jupyter notebook es una aplicación web que permite crear documentos (llamados notebooks de
Jupyter) que contienen código vivo, texto, fórmulas, guras, y medios audiovisuales. Esto facilita una
explicación más detallada y atractiva de los conceptos que se quieren describir. Estos documentos se
visualizan con un navegador (Explorer, Chrome, Firefox, ...) y pueden incluir cualquier elemento accesible
a una página web, además de permitir la ejecución de código escrito en el lenguaje de programación
Python.

Jupyter reúne en un mismo componente todas las herramientas cientí cas estándar de Python.
Veremos a lo largo de este curso cómo podemos usar este entorno, junto con Python, para realizar taréas
típicas en el contexto del análisis de datos: importación y exportación, manipulación y transformación,
visualización y mucho más.
Puesta en marcha de Jupyter Notebook

Una vez realizada la instalación de Anaconda en cualquiera de los sistemas operativos mencionados, la
ejecución de Jupyter Notebook se puede realizar a través de la aplicación Anaconda Navigator.

También podemos ejecutar Jupyter notebook directamente. Para ello será necesario seleccionar el
acceso directo a dicha aplicación desde el menú Inicio de Windows.

Si su sistema operativo es Mac OS, busque Jupyter Notebook entre sus aplicaciones.

La ejecución de jupyter notebook abrirá una ventana nueva en el navegador de internet.


En este punto es importante conocer que Jupyter se abre en la carpeta home del usuario del equipo. Por
ejemplo, en el sistema operativo Windows se abre en la parpeta C:\Users\name, siendo name el nombre
del usuario. Aparecerán unas cuantas carpetas y tal vez algunos cheros. En realidad esta interfaz web
actúa como un explorador de archivos, por lo que la imagen anterior puede cambiar, ya que depende del
contenido de la carpeta name del equipo. Esta interfaz permite la navegación entre distintas carpetas y
crear cheros.

Mi recomendación es crer una carpeta nueva donde guardaremos el trabajo desarrollado en este curso.
Dicha carpeta puede estar alojada en la carpeta Documentos del usuario.

En este caso, desde la aplicación jupyter notebook podremos selecionar dicha parpeta por lo que se
abrirá una ventana del navegador con un aspecto similar al siguiente:

En la siguiente Sección nos adentraremos en el manejo del entorno de desarrollo Jupyter notebook.
Sección 1.3

El entorno de desarrollo Jupyter Notebook

Jupyter notebook ofrece una herramienta de ejecución interactiva con la cual es posible dar órdenes
directamente al intérprete de Python y obtener una respuesta inmediata para cada una de ellas.

Jupyter Notebook es una aplicación web que permite crear documentos que contienen código y texto
(entre otros elementos). Estos documentos se visualizan con el navegador (Explorer, Chrome, Firefox, ...).

Los notebooks de Jupyter

Son archivos con extensión .ipynb. En ellos podemos escribir código python ejecutable, texto, dibujar
grá cas y mucho más.

Un notebook de Jupyter es un chero que contiene un conjunto de celdas donde cada celda puede ser de
distintos tipos:

Markdown: sirven para escribir texto.


Code: sirven para escribir código ejecutable. Están marcadas por la palabra In [n] y están
numeradas. El resultado de la ejecución de cada celda se muestra en el campo Out[n], también
numerado.

A continuación se muestra un ejemplo de una celda de código:

# celda de código. Esta línea es un comentario


3 + 4

Como podemos ver en el ejemplo anterior, ambas celdas tienen el mismo número, indicando que la
segunda es el resultado de la primera. La primera celda In [ ] contiene código a ejecutar y la segunda
celda Out[ ] se genera automáticamente como resultado de la ejecución de la primera.

Los comentarios dentro de secciones de código se hace poniendo el símbolo de gato, #, al inicio de la
línea.

Raw NBConvert: Son celdas que permiten escribir fragmentos de código sin que sean ejecutados.

Crear un nuevo notebook

Para crear un nuevo notebook es necesario desplegar el botón New y posteriormente seleccionar Python
3.
El efecto es que se abrirá otra pestaña en el navegador con un documento nuevo y sin nombre. Mejor
dicho, Jupyter le da un nombre por defecto (untitled) y que nosotros debemos cambiar una vez creado.

Para cambiar el nombre es necesario pinchar sobre la palabra Untitled y aparecerá una nueva ventana
donde podremos escribir el nuevo nombre.

Ejecución de las celdas

Todas las celdas son susceptibles de ser ejecutadas. La ejecución de una celda de código Python,
ejecutará el código escrito en la celda y producirá una salida, que será el resultado de la ejecución. La
ejecución de celdas de tipo Markdown, dará formato al texto.

Para ejecutar una celda tienes que posicionarte en la celda y posteriormente pulsar el botón cuyo icono
es un triángulo mirando a la derecha.
Para crear celdas nuevas pulsaremos el botón con el icono del signo +. En la siguiente imagen puedes ver
distintas celdas de texto. La primera celda es de código, la segunda y cuarta son celdas de texto sin
ejecutar, mientras que la tercera es una celda de texto ya ejecutada. Si haces doble click en una celda ya
ejecutada, pasa a ser una celda no ejecutada y podrás modi carla añadiendo o eliminando texto. Luego
podrás volver a ejecutarla.

Como puedes observar, el menú es bastante intuitivo. Navega un poco por él a ver qué descubres.

¿Qué se puede hacer en un Notebook de IPython?


Dibujar grá icas

import scipy.special as spec


%pylab inline
x = np.linspace(0, 20, 200)
for n in range(10,15,3):
plt.plot(x, spec.jn(n, x), label=r'$J_{%i}(x)$' % n)
grid()
legend()
title('Bessel Functions');

Populating the interactive namespace from numpy and matplotlib

Incrustar Imágenes

from IPython.display import Image


Image(filename='./images/setas.jpg')

Incrustar vídeos
from IPython.display import YouTubeVideo
YouTubeVideo('SLCuL-K39eQ') # Para probarlo ejecutar esta celda

Night On Bald Mountain - Fantasia (1941) (Theatrical Cut)

Escribir y representar código HTML

from IPython.display import HTML


s = """<table>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
<tr>
<td>row 1, cell 1</td>
<td>row 1, cell 2</td>
</tr>
<tr>
<td>row 2, cell 1</td>
<td>row 2, cell 2</td>
</tr>
</table>"""
h = HTML(s)
h

Header 1 Header 2

row 1, cell 1 row 1, cell 2

row 2, cell 1 row 2, cell 2

Escribir código cientí ico


from IPython.display import Math
Math(r'F(k) = \int_{-\infty}^{\infty} f(x) e^{2\pi i k} dx')

𝐹 (𝑘) = ∫ 𝑓 (𝑥)𝑒2𝜋 𝑑𝑥
𝑖𝑘

−∞

𝑐= √‾𝑎‾‾‾‾‾
2
+ 𝑏‾2

Características del entorno

Introspección: Usando el símbolo ? es posible conocer información de un objeto:

b = [1,2,3]
b?
# Para probarlo ejecutar esta celda

Si usamos el símbolo ? sobre un método, se visualiza la información de lo que hace el método:

b.append?
# Para probarlo ejecutar esta celda

Y si usamos dos símbolo ??, se muestra el código del método cuando sea posible.
También se puede combinar con el símbolo * para realizar búsquedas:

b.*p*?
# Para probarlo ejecutar esta celda

Uso del tabulador (tab completion)

Mientras se escribe una expresión, si se pulsa el tabulador IPython ofrece ayuda para completar
la expresión.
El uso del tabulador también sirve para buscar los métodos de un objeto
También se usa para completar rutas de cheros.

b.remove?

b.append(2)
b

[1, 2, 3, 2]

Comandos mágicos

Son comandos especiales que permiten al usuario tener un control del comportamiento del
sistema.
Están precedidos por el símbolo %.

Ejemplos:
%reset : Limpia el espacio de trabajo actual y borra todos los nombres
de nidos por el usuario. Cuando traajamos con grandes volúmenes
de datos, Jupyter los mantiene en memoria incluso después de hacer
%del, por lo que no se libera memoria. En estos casos resulta útil.

%time : Sirve para medir el tiempo de ejecución de una instrucción .


Ejecuta la instrucción una sola vez y mide el tiempo empleado. Muy
útil para comparar procesos con la misma nalidad pero que utilizan
algoritmos diferentes.

%timeit : Permite obtener una medida más precisa. En este caso, se


ejecuta la instrucción varias veces y se calcula la media de los tiempos.

Cuando se trabaja con grandes volúmenes de datos, unos


pocos milisegundos son muy importantes.

b
%reset
## Para probarlo ejecutar esta celda

b
## Para probarlo ejecutar esta celda para ver qué ha ocurrido con la variable b

%timeit 3+4

%time 3+4

time 3+4

Comunicación con el SO
Es posible ejecutar comandos que se utilizan en las consolas de Windows o Linux.

%pwd : devuelve el nombre del directorio de trabajo

%cd dir : cambia el directorio actual de trabajo

%ls : devuelve el contenido del directorio actual

%pwd
## Para probarlo ejecutar esta celda

Referencias

Jupyter notebook documentation


Sección 1.4

Práctica inicial. Toma de contacto.

Esta práctica tiene como objetivo la toma de contacto con el sistema Jupyter, esto es, conocer el entorno
de programación en el que vamos a trabajar en este curso.

Supuesto que ya ha descargado e instalado Anaconda en la versión adecuada para su equipo, realize los
siguientes pasos:

Paso 1

Entrar en la carpeta Documentos de su equipo. Crear una nueva carpeta llamada pythonCFI (si no la
creó anteriormente).

Paso 2

Abrir la aplicación Anaconda Navigator y posteriormente lanzar Jupyter Notebook. Se abrirá una
pestaña nueva en su navegador por defecto. Se habrá abierto una pestaña en el navegador con nombre
Home. Pinchar cobre la carpeta Documentos. Luego muévase a la carpeta pythonCFI pinchando sobre
ella.

Paso 3

Crear un documento nuevo pulsando el botón 'New' y seleccionando 'Python 3'. Observar que se ha
abierto una nueva pestaña en el navegador.

Paso 4

Cambiar el nombre al documento recién creado. Su nuevo nombre es 'documento_inicial'. En los


siguientes pasos se crearán y ejecutarán varios tipos de celdas.

Paso 5
Por defecto las celdas son de tipo code. Seleccionar la primera (y única) celda y cámbiele el tipo a
Markdown. Escribir en dicha celda el contenido del siguiente chero de texto. Ejecutar la celda y observar
el resultado.

Paso 6

Crear una nueva celda de tipo code. Escribir en ella la siguiente expresión:

8 * (5 + 4)

Posteriormente ejecutar la celda y observar el resultado.

Paso 7

Crear una nueva celda de tipo code y escribe la siguiente expresión:

%pwd

Posteriormente, ejecutar la celda y observar el resultado. Se trata de un comando que permite conocer la
carpeta en el que se encuentra en este momento.

Paso 8

Pulsa el botón que permite grabar el documento. Posteriormente ve a la pestaña con nombre Home.
Observa que ahora hay un documento llamado documento_inicial.

Paso 9 y último

Ve al explorador de archivos, busca la carpeta pythonCFI. Compruebe que en esta carpeta se encuentra
el chero documento_inicial.ipynb.

Realice la entrega de este chero a través del Campus Virtual.


Sección 2.1

Elementos básicos del lenguaje

En esta Sección hacemos un repaso de algunos elementos básicos del Python y que se presupone
conocidas para el estudiante. Si desear profundizar en los conceptos que se verán en esta sección,
recomendamos realizar el curso CFI de Introducción a la programación en Python.

Ejecución en Jupyter notebook

Jupyter notebook ofrece una herramienta de ejecución interactiva con la cual es posible dar órdenes
directamente al intérprete y obtener una respuesta inmediata para cada una de ellas.

Python es un lenguaje interpretado. El intérprete actúa como una simple calculadora. Es posible
introducir una expresión y éste escribirá el resultado de evaluar la expresión.

La sintaxis es sencilla: los operadores +, -, * y / funcionan como en la mayoría de los lenguajes; los
paréntesis (()) pueden ser usados para agrupar expresiones.

Veamos algunos ejemplos:

# esto es un comentario y no se ejecuta

# operación asrimética
2 + 4 - 5

2.3 + 8 # la coma decimal se representa como un punto en Python

10.3

total = 2 ** 3 # operación potencia


total # muestra el resultado de la variable total

print(total)

8
Las secuencias en Python

Tuplas

Una tupla es un tipo de datos que representa una secuencia de valores de cualquier tipo, a los cuales se
puede acceder mediante índices enteros. Las tuplas se caracterizan porque son inmutables.

Desde el punto de vista sintáctico, una tupla se de ne mediante una lista de valores separados por
comas y encerradas entre paréntesis.

# Ejemplos de Tuplas
tup1 = ( 8, 4 ) # colección de elementos (posiblemente de distintos tipos)
tup2 = ( 'Ana', 10, 'Alberto', 7 )

type(tup2)

tuple

tup1

(8, 4)

La tupla con nombre tup1 tiene dos elementos de tipo int, es decir de tipo entero.

La tupla con nombre tup2 tiene 4 elementos, dos de ellos son de tipo int, y los otros dos elementos son
de tipo str, es decir de tipo string.

Listas

Una lista es similar a una tupla con la diferencia fundamental de que puede ser modi cada una vez
creada. Se trata de un tipo de datos mutable y contiene datos heterogéneos.

Para crear una lista se utiliza la notación de corchetes. Se encierran los valores que forman la lista entre
corchetes.

# Ejemplos de listas
data1 = [2, 3, 4] # colección de elementos (posiblemente de distintos tipos)
data2 = ['Ana', 10, 'Alberto', 7, [ 7 ,8 ]]
data2

['Ana', 10, 'Alberto', 7, [7, 8]]

Algunas operaciones sobre listas son la operación + para concatenar listas:

data1 + data2

[2, 3, 4, 'Ana', 10, 'Alberto', 7, [7, 8]]

la operación o método append() para añadir un elemento a una lista:


# insertar elementos en listas
data3 = [1, -9, 4, 1, 9]
data3.append(0) # inserción al final de la lista
data3

[1, -9, 4, 1, 9, 0]

el operador in para conocer si un elemento está en una lista:

4 in data3

True

La función len devuelve la longitud de una lista:

#longitud de una lista


len(data2)

Una forma de crear secuencias de números enteros es mediante el uso de la función range. Esta función
genera una progresión de números enteros. La forma de crearla es mediante la llamada range(start,
end, step).

Veamos algunos ejemplos:

r = range(10) # usamos la función 'list' que me devuelve una lista


list(r)

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

# generación de listas de enteros en un rango


l = list(range(-5, 5))
l

[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]

# generación de tuplas de enteros en un rango


# entre el -5 y 5 con salto 2
t = range(0, 10, 2)
list(t)

[0, 2, 4, 6, 8]

En el ejemplo anterior, creamos una tupla con nombre t. Dicha tupla contiene los elementos pares
comenzando con el valor 0 y terminando en el 9.

Acceso a elementos de las secuencias

Los elementos de las secuencias pueden ser accedidos mediante el uso de corchetes [ ], como en otros
lenguajes de programación. Podemos indexar las secuencias utilizando la sintaxis:

[<inicio>:<final>:<salto>]

En Python, la indexación empieza por CERO. Veamos algunos ejemplos de cómo se utiliza la notación
inicio: fin: salto.
data4 = [10, 20, 30, 40, 50 ,60]

# Acceso a los elementos: indexación comenzando desde 0


data4[0]

10

#longitud de una lista


len(data4)

data4[1:4] # elementos en el rango [1, 4)

[27, 30, 40]

data4[::2] # desde el principio hasta el final con salto 2


[10, 30, 50]

data4[-1] # recorrido en orden inverso

60

data4[-3:] # recorrido desde el antepenúltimo elemento hasta el final


[40, 50, 60]

Como los objetos de tipo list son mutables, podemos modi car su estructura, por ejemplo cambiando
el elemento almacenado en cualquiera de sus posiciones:

# modificación de los elementos


data5 = ['Ana', 10, 'Alberto']
data5[1] = 9 * 3
data5

['Ana', 27, 'Alberto']

Referencias

Tutorial de Python. Por Guido Van Rossum


Sección 2.2

Las funciones en Python (I)

En Python la unidad de estructuración de los programas son las funciones. Una función consiste en un
conjunto de sentencias que realizan una determinada acción y a la que se le asocia un nombre. Una vez
de nida la función, se puede invocar su ejecución a lo largo del programa mediante su nombre. En
general, la función devolverá como resultado un valor de retorno (aunque no siempre tiene que ser así).

De nimos una función sencilla

def suma_elementos(x, y):


s = x + y
return s

Las funciones tienen una primera línea que sirve para de nirlas, indicando su nombre y los
valores que la función necesita para realizar su tarea. Dichos valores, llamados argumentos se
encuentran dentro de los paréntesis. La de nición de la función siempre termina con dos
puntos. Se declaran con la palabra reservada def.
Las funciones pueden devolver valores usando la palabra reservada return.
Los argumentos pueden ser de varios tipos ( posicionales, por clave y argumentos agrupados).
Aquí solo veremos los dos primeros tipos de argumentos.

Funciones con argumentos posicionales

Veamos algunos ejemplos de funciones con argumentos posicionales:

# función que suma 3 números y devuelve el resultado


# 3 argumentos posicionales. más adelante hablaremos de los argumentos por clave
def suma_tres(x, y, z):
m1 = x + y
m2 = m1 + z
return m2

Podemos utilizar la función anterior para sumar los números 4, 2 y 3. Para ello basta con escribir el
nombre la función junto con los valores 4, 2 y 3 como argumentos. A esto se le conoce como llamada a la
función o invocar a la función:
suma_tres(1,2,8)

11

a = 1
b = 8
# los valores de a y b se sustituyen por 1 y 8 respectivamente en la llamada
resultado = suma_tres (a,2,b)
resultado

11

A las funciones se les puede añadir comentarios, para lo cual basta incluir una cadena entre la línea
dónde se de ne def y el cuerpo usando una tripleta de comillas """.

def suma(x,y):
"""
Returns la suma de los dos parámetros x e y
Parameters
----------
x, y: int, float
A number
Returns
-------
int, float
Suma de los dos parámetros x e y
"""
return x + y

help(suma)

Help on function suma in module __main__:

suma(x, y)
Returns la suma de los dos parámetros x e y
Parameters
----------
x, y: int, float
A number
Returns
-------
int, float
Suma de los dos parámetros x e y

El paso de argumentos a una función se hace por referencia, de manera que las modi caciones que se
hagan sobre los argumentos se mantienen después de la llamada y ejecución de la función:

def addElement(lista, num):


lista.append(num)

a = [1, 2, 4]
addElement(a, 9)
a

[1, 2, 4, 9]

Funciones con argumentos por clave

Los argumentos por clave se usan para indicar valores por defecto y siempre se sitúan después de los
argumentos posicionales. De esta forma podemos crear una función que puede ser invocada con menos
argumentos de los que permite.

# función que suma 4 números y devuelve el resultado


def suma_varios(x, y, z1 = 0, z2 = 0): # 2 argumentos posicionales y 2 por clave
m = x + y + z1 + z2
return m

Esta función puede ser invocada pasando sólo los argumentos obligatorios x e y :

resultado1 = suma_varios(2, 3) # z1 y z2 tomarán los valores por defecto


resultado1

Esta función puede ser invocada pasando los argumentos obligatorios x e y junto con un argumento por
clave:

resultado2 = suma_varios(2, 3, z2 = 1) # z1 toma el valor por defecto 0


resultado2

Esta función puede ser invocada pasando los argumentos obligatorios x e y junto con los dos argumento
por clave:

resultado3 = suma_varios(2, 3, z2 = 1, z1 = 9) # ningún valor por defecto


resultado3

15

Valores devueltos por una función

Una función puede tener varias instrucciones return. Si la ejecución de una función no alcanza ninguna
instrucción return, se devuelve None que es el valor que representa el valor nulo en Python. El tipo de
None es NoneType.

def suma_todos(x, y, z, t):


resultado = x + y + z + t # calcula el resultado pero no devuelve nada
# No hay instrucción return

r = suma_todos(2, 3, 4, 6)
r # r es None

type(r)

NoneType

La ejecución de una función termina en el momento en el cual se alcance una instrucción return.
# función que pretende escribir cuatro cadenas: Uno, Dos, Tres, Cuatro
# si añadimos una instrucción return, no se ejecutan todas las instrucciones
def cuenta():
print('Uno')
print('Dos')
return ('Fin de la función') # la función termina tras ejecutar el return
print('Tres')
print('Cuatro')

cuenta()

Uno
Dos

'Fin de la función'

Las funciones Python pueden devolver múltiples valores. Aquí tenemos otra de las características que
hace a Python atractivo a los programadores.

def orden(a,b):
if a <= b:
return a, b
else:
return b, a

m = orden(7, 1)
m

(1, 7)

La función orden() devuelve una tupla con dos valores.

Semántica de Python

A diferencia de otros lenguajes como C++, Java o Perl, Python no utilliza corchetes o llaves para
estructurar el código. Python utiliza espacios en blanco o tabulaciones para dar estructura a su código.

def hola():
print('Hola Mundo!') # Tabulación al comienzo de ésta linea

hola() # llamada a la dunción 'hola'

Hola Mundo!

def producto(a,b):
res = a * b
print(res) # más o menos espacios al comienzo produce un error de sintaxis
return res

File "<ipython-input-23-57d4c01bc3ff>", line 3


print(res) # más o menos espacios al comienzo produce un error de sintaxis
^
IndentationError: unexpected indent

El uso de tabuladores hace el código más legible. En otros lenguajes el código de la función 'hola' ha de
escribirse utilizando llaves, como se muestra a continuación:

def hola():
{
print('Hola Mundo!')
}

Referencias

Tutorial de Python. Por Guido Van Rossum


Sección 2.3

Los diccionarios de Python

En Python, un diccionario es una colección no ordenada de pares clave - valor donde la clave y el
valor son objetos Python.

Características:

Cada uno de los pares clave - valor se separan por comas.


El acceso a los elementos de un diccionario se realiza a través del valor de la clave.
En otros lenguajes se les conoce como tablas hash.
Los diccionarios se crean utilizando llaves { }.

Crear un diccionario

De la misma forma que con listas, es posible de nir un diccionario directamente con los miembros que va
a contener, o bien inicializar el diccionario vacío y luego agregar los valores.

dic = { } # diccionario vacío


dic

{}

dic = {1:'Lunes', 2:'Martes', 3:'Miercoles' } # diccionario con tres elementos


dic

{1: 'Lunes', 2: 'Martes', 3: 'Miercoles'}

En el siguiente ejemplo mostramos cómo se crea un diccionario con dos elementos:

Las claves son las cadenas 'Hola' y 'Comer'.


Los valores asociados a dichas claves son las listas [ 'Hi', 'Hello' ] y [ 'eat' ]
respectivamente.

d = {'Hola' : ['Hi', 'Hello'],


'Comer' : ['eat'] }
d

{'Hola': ['Hi', 'Hello'], 'Comer': ['eat']}


La clave de un diccionario puede ser cualquier variable de tipo inmutable:

cadenas,
enteros,
tuplas (con valores inmutables en sus miembros), etc.

Los valores de un diccionario pueden ser de cualquier tipo: listas, cadenas, tuplas, otros diccionarios,
objetos, etc.

d1 = {'Lunes' : 1, 'Martes' : 2 , 'Finde' : (6,7) }


d1

{'Lunes': 1, 'Martes': 2, 'Finde': (6, 7)}

Otra forma de crear un diccionario es declararlo vacío y luego insertar los valores. Se declara el
diccionario vacío, y luego se asignan valores directamente a las claves.

d2 = { }
d2['Juan'] = 609922565
d2['Ana'] = 691252580
d2['Luis'] = 642589569
d2

{'Juan': 609922565, 'Ana': 691252580, 'Luis': 642589569}

Si la clave existe en el diccionario, la operación de asignación modi ca su valor; si no exite dicha clave,
entonces se crea con el valor indicado en la asignación.

d2['Luis'] = 0 # clave que ya existe y por tanto se actualiza


d2

{'Juan': 609922565, 'Ana': 691252580, 'Luis': 0}

Acceso a los elementos de un diccionario

Para acceder al valor asociado a una determinada clave, utilizamos la notación de corchetes [ ] al igual
que hacíamos con las variables de tipo lista. En el caso de los diccionarios escribimos la clave elegida en
lugar del índice.

d2

{'Juan': 609922565, 'Ana': 691252580, 'Luis': 0}

d2 ['Luis']

Las claves son únicas dentro de un diccionario, es decir que no puede haber un diccionario que tenga dos
veces la misma clave. Si se asigna un valor a una clave ya existente, se reemplaza el valor anterior.
d2 ['Luis'] = 0
d2 ['Ana'] = ['938941523', '609585962']
d2

{'Juan': 609922565, 'Ana': ['938941523', '609585962'], 'Luis': 0}

Si se intenta acceder a una clave que no está en el diccionario, el acceso falla.

d2 ['Carlota']

---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-12-c0fadf735c7d> in <module>
----> 1 d2 ['Carlota']

KeyError: 'Carlota'

Para evitar este tipo de errores, podemos usar la función in, que comprueba si un elemento está en el
diccionario o utilizar el método get(), que devuelve el valor None si la clave no está en el diccionario.

'Carlota' in d2

False

'Luis' in d2

True

m = d2.get('Carlota')
type(m)

NoneType

Eliminar elementos de un diccionario

Para eliminar elementos de un diccionario se utiliza el método pop() con la clave que se desea eliminar:

d2

{'Juan': 609922565, 'Ana': ['938941523', '609585962'], 'Luis': 0}

e = d2.pop('Ana')
e

['938941523', '609585962']

El método pop devuelve como resultado el elemento borrado.

d2

{'Juan': 609922565, 'Luis': 0}

Si intentamos eliminar una clave que no existe, el sistema lanzará un error:


d2.pop('Carlota')
d2

---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-19-25795e3e7ecd> in <module>
----> 1 d2.pop('Carlota')
2 d2

KeyError: 'Carlota'

Otros métodos para la gestión de diccionarios

El método keys() devuelve una lista con las claves del diccionario. El método values() devuelve una
lista con los valores del diccionario.

d2

{'Juan': 609922565, 'Luis': 0}

d2.keys()

dict_keys(['Juan', 'Luis'])

d2.values()

dict_values([609922565, 0])

Recorrer los elementos de un diccionario

Los diccionarios son secuencias de elementos, por lo que se les considera objetos iterables. Para recorrer
dichos elementos se pueden usar la sentencia for. Por ejemplo podemos recorrer las claves del
diccionario y a partir de ellas acceder a los valores.

agenda = {'Ana': 691252580, 'Juan': 609922565, 'Luis': 642589569}


for clave in agenda:
print (clave, ' : ', agenda[clave])

Ana : 691252580
Juan : 609922565
Luis : 642589569

También es posible usar una sentencia for para recorrer los pares clave-valor que devuelve el
método items().

agenda = {'Ana': 691252580, 'Juan': 609922565, 'Luis': 642589569}


for clave,valor in agenda.items():
print (clave, '--', valor)

Ana -- 691252580
Juan -- 609922565
Luis -- 642589569
Ejemplos
Ejemplo 1

El siguiente diccionario

agenda = {'Ana': {'casa':91258471, 'movil': 9057963333},


'Pedro': {'movil':900111111, 'trabajo':609235236},
'Luis': {'casa': 458235236}}

representa la agenda de teléfonos de los clientes de una empresa.

Nos planteamos las siguientes preguntas:

(a) ¿Cuántos clientes están registrados en la agenda?

Solución: Cada cliente se corresponde con un elemento del diccionario. Basta con preguntar por el
número de elementos del diccionario para conocer el número de clientes:

agenda = {'Ana': {'casa':91258471, 'movil': 9057963333},


'Pedro': {'movil':900111111, 'trabajo':609235236},
'Luis': {'casa': 458235236}}

len(agenda)

(b) Escribir las instrucciones necesarias para recuperar la lista de nombres de los clientes.

Solución: Las claves del diccionario representan los nombres de los clientes. Bastará con preguntar por
las claves para obtener los nombres de los clientes. Usatemos la función keys() de los diccionarios:

agenda = {'Ana': {'casa':91258471, 'movil': 9057963333},


'Pedro': {'movil':900111111, 'trabajo':609235236},
'Luis': {'casa': 458235236}}

clientes = agenda.keys()
clientes

dict_keys(['Ana', 'Pedro', 'Luis'])

# para obtener los datos de tipo lista:


list(clientes)

['Ana', 'Pedro', 'Luis']

(c) Escribir las instrucciones necesarias para recuperar todos los teléfonos de Pedro.

Solución: Preguntaremos por el valor asociado a la clave Pedro. Usaremos el operador de acceso
corchete:
agenda = {'Ana': {'casa':91258471, 'movil': 9057963333},
'Pedro': {'movil':900111111, 'trabajo':609235236},
'Luis': {'casa': 458235236}}

telefonos_pedro = agenda['Pedro']
telefonos_pedro

{'movil': 900111111, 'trabajo': 609235236}

Pedro tiene dos teléfonos.

(d) Escribir las instrucciones necesarias para recuperar el teléfono del trabajo de Pedro.

Solución: Recuperaremos todos los teléfonos de Pedro, y posteriormente, buscaremos el valor de la clave
trabajo:

agenda = {'Ana': {'casa':91258471, 'movil': 9057963333},


'Pedro': {'movil':900111111, 'trabajo':609235236},
'Luis': {'casa': 458235236}}

telefonos_pedro = agenda['Pedro']
trabajo_de_pedro = telefonos_pedro['trabajo']
trabajo_de_pedro

609235236

(d) Escribir las instrucciones necesarias para modi car el teléfono del trabajo de Pedro. El nuevo teléfono
es 6000.

Solución: Recuperaremos todos los teléfonos de Pedro, y posteriormente, modi caremos el valor de la
clave trabajo:

agenda = {'Ana': {'casa':91258471, 'movil': 9057963333},


'Pedro': {'movil':900111111, 'trabajo':609235236},
'Luis': {'casa': 458235236}}

telefonos_pedro = agenda['Pedro']
telefonos_pedro['trabajo'] = 6000

Podemos comprobar que la agenda se ha modi cado y el teléfono del trabajo de Pedro ha cambiado:

agenda

{'Ana': {'casa': 91258471, 'movil': 9057963333},


'Pedro': {'movil': 900111111, 'trabajo': 6000},
'Luis': {'casa': 458235236}}

Referencias

Tutorial de Python. Por Guido Van Rossum


Sección 2.4

Las funciones en Python (II)

Funciones como argumentos de otras funciones.

El uso de funciones como argumentos de otras funciones es una característica de los lenguajes
funcionales.

Para mostar un ejemplo, de nimos en primer lugar una función que calcula el factorial de un número.

def factorial(num):
resultado = 1
for i in range(1, num + 1 ):
resultado = resultado * i
return resultado

Podemos probar la función anterior con el número 3. Así, el resultado devuelto por la función será 6.

factorial(3)

Supongamos ahora que queremos calcular el factorial de una lista de números. Una opción es crear una
segunda función con dos argumentos (la vamos a llamar aplicar_operacion). El primer argumento es
una función f y el segundo argumento es una lista m. La idea es que la función aplicar_operacion
aplique la función f a cada uno de los elementos de la lista m.

Veamos:

def aplicar_operacion(f, m):


nueva_lista = []
for e in m:
resultado = f(e) # invocamos a la función f con el argumento e
nueva_lista.append(resultado)
return nueva_lista

Ahora, podemos calcular el factorial de la lista de números [3,4,2,5] ejecutando el siguiente código:

aplicar_operacion(factorial, [3,4,2,5])

[6, 24, 2, 120]

Como podemos ver, la función factorial se ejecuta sobre cada uno de los elementos de la lista [3, 4,
2, 5].

Los bene cios que me proporciona la función aplicar_operacion es que su primer parámetro puede
ser cualquier función. Por ejemplo, podemos calcular el valor absoluto de cada uno de los elementos de
una lista invocando a la función aplicar_operacion con el parámetro abs.

aplicar_operacion(abs, [3,-4, 2,-5])

[3, 4, 2, 5]

La función abs es una función de Python que calcula el valor absoluto de un número.
La función map de Python se comporta de forma similar a la función aplicar_operacion. Aplica una
función a una colección de objetos:
En el siguiente ejemplo, la función map aplica la función abs a cada uno de los elementos de la lista de su
segundo parámetro:

m1 = map(abs , [2, -5,-58])


list(m1)

[2, 5, 58]

En el siguiente ejemplo, se calcula el factorial de cada uno de los elementos de la lista en el segundo
argumento.

m1 = map(factorial , [2, 5, 8])


list(m1)

[2, 120, 40320]

Funciones anónimas (lambda functions)

Las funciones anónimas son aquellas que no tienen nombre y se re eren a una única instrucción. Se
declaran con la palabra reservada lambda.

Su sintaxis es:

lambda e : expresión

dondee representa el argumento de entrada de la función y expresión representa el valor devuelto de


la función.

Son funciones cortas. Están sintácticamente restringidas a una sola expresión y permiten escribir
funciones de forma más sencilla.

Las funciones Lambda pueden ser usadas en cualquier lugar donde sea requerido un objeto de tipo
función.

Supongamos que queremos calcular el doble de un número. La función siguiente realiza esa operación:
# función normal
def doble(a):
"""
Calcula el doble de un número
"""
return a * 2

La función anónima equivalente:

lambda x: x * 2

<function __main__.<lambda>(x)>

La función lambda anterior la podemos usar directamente como parámetro de la función


aplicar_operacion:

# doble de todos los números de una lista


lista= [1, 2, 3, 4 ]
aplicar_operacion(lambda x: x * 2, lista)

[2, 4, 6, 8]

O junto con la función map:

# doble de todos los números de una lista


lista= [1, 2, 3, 4 ]
list(map(lambda x: x * 2, lista) )

[2, 4, 6, 8]

Las funciones lambda se utilizan mucho en análisis de datos ya que es muy usual transformar datos
mediante funciones que tienen a otras funciones en sus argumentos. Se usan funciones lambda en lugar
de escribir funciones normales para hacer el código más claro y más corto.

Ejemplos

Ejemplo 1:

Dada la lista de NIF´s:

m = ['12345678N', '8745444J', '85444478B' ]

Utilizar la función map junto con una función lambda para construir una lista que solo tenga la letra de
cada uno de los NIF's. El resultado será:

['N', 'J', 'B']


# Sol:
m = ['12345678N', '8745444J', '85444478B' ]
resultado = map(lambda x: x[-1], m)
list(resultado)

['N', 'J', 'B']

En este caso, cada elemento de la lista m es de tipo string. El acceso a cada uno de los caracteres que
forman un nif, se realiza usando el operador de corchete junto con el índice al que queremos acceder.
Por ejemplo, para acceder a la primera posición, usaremos el índice 0.

e = '12346678N'
e[0]

'1'

De esta forma, con índices negativos podemos acceder a las posiciones en sentido inverso, es decir, de
derecha a izquierda. Así, la última posición está en el índice -1.

e = '12346678N'
e[-1]

'N'

La función lamda lambda x: x[-1] toma como entrada el parámetro x (que será de tipo string), y
devuelve la última posición de dicho string.

Ejemplo 2

Dada una lista de direcciones de correo electrónico, se desea extraer el nombre de los usuarios.

direcciones = ['[email protected]', '[email protected]', '[email protected]' ]

Por ejemplo, a partir de la lista anterior, queremos obtener la lista:

['ana', 'luis', 'tu' ]

Para resolver este problema, tenemos que usar la fnción split de los strings, que permite dividir un
string en partes, devolviendo una lista de cadenas que componen el string. Ejecuta la siguiente celda para
ver la ayuda de dicha función

str.split?

Por ejemplo, si queremos partir el string nombre por el espacio en blanco, escribiremos:

nombre = 'Ana González Gil'


nombre.split(' ')

['Ana', 'González', 'Gil']

Si queremos partir el string datos por el guión, escribiremos:


datos = 'Ana González Gil - 10 puntos'
datos.split('-')

['Ana González Gil ', ' 10 puntos']

En el caso de los emails, será buena idea partir el string por el símbolo @:

datos = '[email protected]'
datos.split('@')

['gonzalo', 'ucm.es']

Si observamos el resultado, podemos ver que el nombre del ususario de correo se encuentra en el índice
0 (primera posición de la lista devuelta por la función split.

Con todo lo anterior, ya podemos resolver el problema. Extraer el nombre de los usuarios a partir de una
lista de direcciones de correo:

direcciones = ['[email protected]', '[email protected]', '[email protected]' ]


resultado = map(lambda x: x.split('@')[0], direcciones)
list(resultado)

['ana', 'luis', 'tu']

Referencias

Tutorial de Python. Por Guido Van Rossum


Sección 2.5

Ejercicios

Ejercicio 1

(a) Utiliza la función range para generar la lista de numeros impares con inicio 5 y n 101. Ambos
inclusive.

(b) Utiliza la función range para generar la tupla de numeros múltiplos de 10 con inicio 100 y n 10.
Ambos inclusive. El resultado será: (100, 90, ... , 10).

# Sol a)

# Sol b)

Ejercicio 2

Los ejemplos presentados en la Sección 2.3 te ayudarán a resolver el siguiente ejercicio.

El siguiente diccionario representa los datos de distintas ciudades:

ciudades = {
'Torremocha de Jarama': {'población': 942,
'superficie': 18.49,
'altitud': 707},
'Torres de la Alameda': {'población': 7825,
'superficie': 43.79,
'altitud': 658},
'Tres Cantos': {'población': 46,
'superficie': 37.93,
'altitud': 710},
'Valdaracete': {'población': 643,
'superficie': 64.31,
'altitud':733},
'Valdeavero': {'población': 1450,
'superficie': 18.79,
'altitud': 717},
'Valdelaguna': {'población': 867,
'superficie': 42.13,
'altitud': 700},
'Valdemanco': {'población': 916,
'superficie': 17,
'altitud': 1125}
}
(a) Escribe las instrucciones necesarias para recuperar la lista de todas las ciudades.

# Sol:

(b) Escribe la instrucción que devuelve la población de Valdelaguna.

# Sol:

(c) Aumenta en dos unidades la super cie de Valdemanco.

# Sol:

Ejercicio 3

Los ejemplos presentados en la Sección 2.4 te ayudarán a resolver el siguiente ejercicio.

Dada una lista de direcciones de correo electrónico:

direcciones = ['[email protected]', '[email protected]', '[email protected]' ]

se desea extraer el nombre del dominio. Por ejemplo, a partir de la lista anterior, queremos obtener la
lista:

['gmail.com', 'google.es', 'ucm.es' ]

Para resolver este problema, tenemos que usar la fnción split de los strings, que permite dividir un string
en partes.

# Sol:

Ejercicio 4

Los contenidos presentados en la Sección 2.4 te ayudarán a resolver el siguiente ejercicio.

(a) Construye una función llamada calcular_iva que reciba como parámetro de entrada un valor
numérico que representa un importe. La función devuelve el 21% de IVA de dicho importe.

(b) Dada una lista de importes, queremos calcular el 21% de IVA de cada importe. Usa la función
aplicar_operacion de nida en la sección anterior para aplicar la función calcular_iva de nida en el
apartado anterior, a una lista de importes.

(c) Crea la función lambda equivalente a la función creada en el apartado (a).

Ejemplos:

calcular_iva(100) devuelve como resultado 21.0


aplicar_operacion(calcular_iva, [100,200]) devuelve como resultado la lista [21.0,
42.0]

# Función creada en la sección 2.4:


def aplicar_operacion(f, m):
nueva_lista = []
for e in m:
resultado = f(e)
nueva_lista.append(resultado)
return nueva_lista

# Sol:
Sección 3.1

Importar módulos

Los módulos son programas que amplían las funciones y clases de Python para realizar tareas
especí cas.

Lo habitual cuando se desarrollan aplicaciones es que los programas se vuelvan muy largos. En estos
casos conviene organizar el código en distintos archivos dependiendo de su funcionalidad. Con esto
conseguimos:

que el mantenimiento sea más fácil.


reutilizar código; usar una función en varias aplicaciones sin necesidad de copiarla varias veces.

Estos archivos se llaman módulos y tienen extensión py.

Contienen de nición de funciones


Datos
De nición de clases, etc.

En https://docs.python.org/3/py-modindex.html se puede encontar el índice de módulos de Python.

Módulos estándar

Python viene con una biblioteca de módulos estándar. Algunos módulos se integran en el intérprete. Los
módulos integrados proporcionan operaciones que no son parte del núcleo del lenguaje (por ejemplo
primitivas del sistema operativo).

Para listar los módulos que tenemos instalados y que por tanto podemos importar en nuestros
programas, basta con ejecutar el siguiente comando:

!conda list

Si queremos usar un módulo que no está en la lista, tendremos que instalarlo primero. Para ello
podemos usar el comando conda install desde una ventana de comandos. Por ejemplo, para instalar
el módulo pymysql escribiremos:
conda install pymysql

Los módulos que tenemos disponibles se pueden importar de las siguientes formas:

* from modulo import *


* import modulo
* import modulo as alias

Dependiendo de cómo hayamos importado el módulo, así tendremos que utilizarlo.

Si necesitamos usar solo una función de un módulo, usaremos la notación:

from <modulo> import <función>

El siguiente código permite importar la función array de nida en el módulo numpy:

from numpy import array # Importamos solo la función array


a = array( [2,3,4] )
a

array([2, 3, 4])

Si queremos importar el módulo completo, usaremos la notación:

import <modulo>

El siguiente código permite importar el módulo numpy:

import numpy # Importamos todo el módulo


a = numpy.array( [2,3,4] )
a

array([2, 3, 4])

Si importamos todo el módulo con import numpy, para usar las funciones de nidas dentro del módulo
debemos poner el nombre del módulo delante del nombre de la función. Por ejemplo, para usar la
función array, es necesario escribir numpy.array.

Por último, Python permite referirnos a un módulo a través de un nombre corto o alias. Si queremos
importar el módulo completo con alias , usaremos la notación:

import <modulo> as <alias>

El siguiente código permite importar el módulo numpy con alias `np:

import numpy as np
a = np.array( [2,3,4] ) # se accede con el alias a la función array
a

array([2, 3, 4])

Para usar las funciones de nidas dentro del módulo, debemos poner el alias del módulo delante del
nombre de la función. Por ejemplo, para usar la función array, es necesario escribir np.array.

ó
Operaciones disponibles en un módulo

La función integrada dir() se usa para encontrar qué nombres de ne un módulo (constantes,
funciones, etc.). Devuelve una lista ordenada de cadenas:

# Ejecuta esta celda

import math
dir(math) # math es un módulo con operaciones matemáticas

import math as mt
mt.sin(34) # sin es la función que calcula el seno

0.5290826861200238

mt.pi # constante pi definida en el módulo math


3.141592653589793

mt.sqrt(24)

4.898979485566356

help(mt.pow) # accede a la documentación de la función pow


Help on built-in function pow in module math:

pow(...)
pow(x, y)

Return x**y (x to the power of y).

Referencias

Tutorial de Python. Por Guido Van Rossum


Sección 3.2

Los arrays de NumPy

La librería NumPy es la librería básica de Python para realizar cálculo cientí co. Proporciona una
estructura de datos para representar arrays multidimensionales y un conjunto de funciones y métodos
muy e cientes para operar con dichos arrays. Esta e ciencia se consigue en parte a la eliminación de
recorridos explícitos (for o while) de los elementos del array. Sobre ella se desarrollan otras librerías de
cálculo cientí co de más alto nivel como Pandas o Scipy.

En 1995, Jim Hugunin desarrolló la librería Numeric como una primera aproximación a lo que hoy es
NumPy. Más tarde desarrolló la librería Numarray, mejorando la e ciencia de algunas operaciones sobre
arrays. Durante un tiempo ambas librerías coexistieron, hasta que en el año 2006, el desarrollador Travis
Oliphant lanza la versión 1.0. de NumPy como resultado de fusionar las dos librerías.

Actualmente, NumPy es una de las librerías más usadas en aquellas aplicaciones que involucran arrays
multidimensionales y arrays de gran tamaño. En este capítulo presentamos la estructura de datos
ndarray, la cual permite representar arrays multidimensionales. Veremos cómo crear dichos arrays, sus
propiedades y las operaciones disponibles sobre ellos. Haremos especial hincapié en las llamadas
operaciones vectorizadas, como un mecanismo de cómputo e ciente para este tipo de estructuras.

Importando la librería

Para hacer uso de la librería NumPy es necesario importarla. Para importar la librería NumPy debemos
escribir una de las siguientes instrucciones:

import numpy
import numpy as np

Las dos instrucciones tienen el mismo efecto, con la diferencia de que la segunda permite utilizar las
funciones, datos, etc. de NumPy usando el alias o nombre abreviado (namespace) np en lugar de numpy.

Los Arrays de NumPy

La librería NumPy de ne un nuevo tipo de datos con un conjunto de propiedades (o atributos) y unas
operaciones (o métodos). Se trata del tipo de datos ndarray; nos referiremos a ellos como arrays. Los
arrays de NumPy son colecciones de elementos homogéneos y de tamaño jo. Con el término
homogéneo, nos referimos a que todos los elementos del array tienen el mismo tipo. Esta es una
característica que no tiene por qué cumplirse en el caso de las listas en Python, en las que los elementos
pueden ser de tipos distintos. De tamaño jo porque una vez creados no se pueden redimensionar como
ocurre con las listas.

Para crer un array es necesario utilizar la función np.array. Esta función es la constructora de arrays. A
continuación mostramos cómo crear un array a partir de una lista.

datos = np.array( [[1,2,3],[4,5,6]] )


datos

array([[1, 2, 3],
[4, 5, 6]])

La variable datos es de tipo ndarray como se puede observar tras preguntar al intérprete por su tipo:

type(datos)

numpy.ndarray

Cada array de NumPy tiene una serie de propiedades, como por ejemplo, el tipo de datos de los
elementos que contiene, la dimensión del array, etc.
shape que indica las dimensiones del array
dtype indica el tipo de los elementos almacenados en el array
nbytes indica el número de bytes necesarios para almacenar los datos que contiene
ndim indica el número de dimensiones
size indica el número total de elementos en el array
La lista completa de propiedades de los arrays puede obtenerse ejecutando la siguiente instrucción:

np.ndarray?

Podemos consultar las propiedades de un array usando la notación punto . , seguido del nombre de la
propiedad a consultar. A continuación mostramos unos ejemplos de cómo se puede consultar las
características del array datos:

datos.shape

(2, 3)

datos.size

datos.dtype

dtype('int32')
datos.nbytes

24

El array datos contiene 6 elementos de tipo int32, necesitando un total de 24 bytes para poder
almacenarlo.

Los tipos de datos básicos en NumPy para realizar cálculo numérico son int(para enteros), float(para
números en coma otante) y complex(números complejos). Cada uno de estos tipos tiene variantes
dependiendo del tamaño.

int8 : Integer Byte (-128 to 127)


int16: Integer (-32768 to 32767)
int32: Integer (-2147483648 to 2147483647)
int64: Integer (-9223372036854775808 to 9223372036854775807)
uint8: Unsigned integer (0 to 255)
uint16: Unsigned integer (0 to 65535)
uint32: Unsigned integer (0 to 4294967295)
uint64: Unsigned integer (0 to 18446744073709551615)

oat16: Half precision oat: sign bit, 5 bits exponent, 10 bits mantissa
oat32: Single precision oat: sign bit, 8 bits exponent, 23 bits mantissa
oat64: Double precision oat: sign bit, 11 bits exponent, 52 bits mantissa
También se pueden crear arrays de tipo string.

s = np.array(['Aa', 'Bb', 'Cc'])


s

array(['Aa', 'Bb', 'Cc'], dtype='<U2')

s.dtype

dtype('<U2')

No suele ser necesario elegir de forma explícita el tamaño del tipo de datos a la hora de crear un array,
pero sí puede ser útil crear arrays de un cierto tipo, ya sea int, float o complex, lo que garantizará la
e ciencia de las operaciones que se vayan a realizar.

En los siguientes ejemplos, mostramos cómo crear arrays cuyos elementos son de tipos int, float y
complex. El tipo de datos complex sirve para representar los números complejos en Python.

datos_i = np.array([1, 2, 4], dtype=np.int)


print(datos_i)

[1 2 4]
datos_f = np.array([1, 2, 4], dtype=np.float)
print(datos_f)

[1. 2. 4.]

datos_c = np.array([1, 2, 4], dtype=np.complex)


print(datos_c)

[1.+0.j 2.+0.j 4.+0.j]

El tipo de datos de un array no se puede cambiar una vez que se ha creado. Lo que sí podemos hacer es
crear un nuevo array como copia de otro usando la función np.array indicando en el argumento dtype
el nuevo el tipo de datos:

datos = np.array(datos_f, dtype=np.int)


datos

array([1, 2, 4])

Como podemos ver, se ha realizado promoción de tipos; el tipo float ha promocionado al tipo int.
También podemos usar la función astype para crear un array a partir de otro, pero con tipo diferente.

datos_f = np.array([1, 2, 4], dtype=np.float)


datos = datos_f.astype(np.complex)
datos

array([1.+0.j, 2.+0.j, 4.+0.j])

Cuando se opera con arrays, el intérprete de Python realiza promoción automática de tipos si la
operación lo requiere. En el siguiente ejemplo podemos observar que el resultado de la suma de un array
de tipo int y un array de tipo float es un array de tipo float.

datos_i = np.array([1, 2, 4], dtype=np.int)


datos_f = np.array([1., 2., 4.], dtype=np.float)
resultado = datos_i + datos_f
resultado

array([2., 4., 8.])

resultado.dtype

dtype('float64')

Creación de arrays

En la sección anterior hemos podido ver cómo crear e inicializar arrays mediante la función np.array. A
continuación presentamos otras formas de crear arrays dependiendo de nuestras necesidades. Por
ejemplo, crear arrays con datos que siguen unas reglas, con datos aleatorios o con datos constantes.
También presentamos el mecanismo para crear arrays a partir de datos contenidos en un chero. La lista
completa de funciones se puede consultar en el manual de referencia de NumPy.

Creación de arrays a partir de listas u otros arrays


La función np.array permite crear arrays a partir de una lista, un objeto iterable u otro array
previamente creado. Por ejemplo, podemos crear un array a a partir de una lista de enteros y
posteriormente crear un array b como una copia del array a.

a = np.array([3,5,7,9])
a.ndim

b = np.array(a)
b

array([3, 5, 7, 9])

El siguiente ejemplo muestra cómo se crea un array c a partir de un objeto iterable. En este caso se trata
del objeto range(10,14).

c = np.array(range(10,14))
c

array([10, 11, 12, 13])

Creación de arrays con valores constantes

Las funciones np.zeros y np.ones permiten crear arrays cuyos datos son todo ceros y unos,
respectivamente. Como argumentos de ambas funciones es necesario indicar las dimensiones del array
mediante una tupla o una lista. Por ejemplo, para crear un array de ceros de dimensión 3 las y 4
columnas, hacemos lo siguiente:

a1 = np.zeros( (3,4) )
a1

array([[0., 0., 0., 0.],


[0., 0., 0., 0.],
[0., 0., 0., 0.]])

A continuación mostramos cómo crear un array a2 de unos de con 1 la y 7 columnas:

a2 = np.ones(7)
a2

array([1., 1., 1., 1., 1., 1., 1.])

a2.ndim, a2.shape

(1, (7,))

Tanto np.zeros como np.ones crean arrays cuyos elementos son por defecto de tipo oat. Para crear
arrays de otro tipo es necesario utilizar el argumento dtype indicando el tipo.

a3 = np.zeros((2,4), dtype=np.int16 )
a3

array([[0, 0, 0, 0],
[0, 0, 0, 0]], dtype=int16)
a4 = np.ones((3,2), dtype=np.float32 )
a4

array([[1., 1.],
[1., 1.],
[1., 1.]], dtype=float32)

Las funciones np.zeros_like y np.ones_like son análogas a np.zeros y np.ones pero no es


necesario indicar las dimensiones, solo hay que indicar el array del cual queremos copiar las
dimensiones.

a5 = np.zeros_like( a4 )
a5

array([[0., 0.],
[0., 0.],
[0., 0.]], dtype=float32)

a6 = np.ones_like( a4 )
a6

array([[1., 1.],
[1., 1.],
[1., 1.]], dtype=float32)

Si lo que queremos es crear un array cuyos valores son una constante distinta de 0 ó 1, podemos utilizar
la función np.full. En el siguiente ejemplo mostramos cómo crear un array de 5 elementos cuyos
valores son todos 9.5.

b1 = np.full( 7 , 9.5 )
b1

array([9.5, 9.5, 9.5, 9.5, 9.5, 9.5, 9.5])

NumPy también ofrece la función fill para rellenar un array previamente creado con un valor
constante.

b2 = np.zeros((2, 5))
b2.fill(10)
b2

array([[10., 10., 10., 10., 10.],


[10., 10., 10., 10., 10.]])

Creación de arrays como una secuencia de valores

NumPy proporciona mecanismos para crear arrays cuyos datos son una secuencia valores equidistantes.
Se trata de las funciones np.arange y np.linspace. Ambas requieren como valor de sus argumentos el
valor inicial y nal de la secuencia. La función np.arange requiere además el incremento o paso,
mientras que la función np.linspace requiere como argumento adicional el número total de valores de
la secuencia. Por ejemplo, para crear una secuencia de valores entre el 1 y el 5 con incremento 0.5
escribimos lo siguiente:
a = np.arange(start = 1, stop = 5, step = 0.5)
print(a)

[1. 1.5 2. 2.5 3. 3.5 4. 4.5]

O lo que es lo mismo:

a = np.arange(1, 5, 0.5)
print(a)

[1. 1.5 2. 2.5 3. 3.5 4. 4.5]

Como se puede observar, la función np.arange no incluye el último valor (en el ejemplo el 5), mientras
que la función np.linspace si lo incluye. Como la función np.arange utiliza argumentos de tipo oat,
no es cómodo predecir el número de elementos del array. En ese caso es mejor utilizar la función
np.linspace que genera un array con un número determinado de elementos sin indicar el incremento.

b = np.linspace(1, 10, 5)
b

array([ 1. , 3.25, 5.5 , 7.75, 10. ])

El array b es una secuencia de 5 valores entre el 1 y el 10.

Creación de arrays usando números aleatorios

El módulo numpy.random ofrece una colección de funciones para generar arrays cuyos datos son valores
aleatorios simples y algunas distribuciones estadísticas. Por ejemplo, la función np.random.rand
premite generar una secuencia de números aleatorios procedentes de una distribución uniforme en el
intervalo [0,1). En el siguiente ejemplo utilizamos la función np.random.rand para generar un único
valor aleatorio:

v = np.random.rand()
v

0.2584688148087325

También podemos crear arrays indicando las dimensiones mediante los argumentos de la función. Por
ejemplo, para generar un array de dimensión 3 las y 4 columnas con valores aleatorios hacemos lo
siguiente:

u2 = np.random.rand(3, 4)
u2

array([[0.65844819, 0.99894686, 0.64122232, 0.94891151],


[0.56297114, 0.05079491, 0.58429779, 0.17646658],
[0.11209657, 0.61261281, 0.97750457, 0.16261527]])

En realidad lo que se genera no son números verdaderamente aleatorios, sino que son pseudoaleatorios.
El generador de números parte de una semilla (normalmente la hora del sistema) y aplicando algoritmos
genera estos números. A veces nos puede interesar jar la semilla para generar la misma secuencia de
números aleatorios (por ejemplo para pruebas). Utilizando la función np.random.seed imponemos las
condiciones iniciales del generador.
np.random.seed(3)
u3 = np.random.rand(4)
u3

array([0.5507979 , 0.70814782, 0.29090474, 0.51082761])

La función np.random.randint permite generar valores de tipo int siguiendo una distribución
uniforme en un intervalo dado. En el siguiente ejemplo utilizamos la función np.random.randint para
generar un valor entero aleatorio en el intervalor [0, 10).

ri = np.random.randint(10)
ri

Podemos usar el argumento size para indicar las dimensiones del array a generar, junto con los límites
del intervalo:

vi = np.random.randint(low = 10, high = 50, size =(3, 2))


vi

array([[20, 20],
[31, 48],
[42, 30]])

O lo que es lo mismo:

vi = np.random.randint(10, 50, (3, 2))


vi

array([[39, 49],
[24, 36],
[27, 36]])

La función np.random.randn permte generar números aleatorios siguiendo una distribución normal
(media 0 y desviación estándar 1).

n = np.random.randn()
n

0.8813180422075299

n = np.random.randn(3,2)
n

array([[ 1.70957306, 0.05003364],


[-0.40467741, -0.54535995],
[-1.54647732, 0.98236743]])

Como podemos observar en la siguiente gura, las funciones np.random.rand y np.random.randint


generan valores siguiendo distribuciones uniformes. Ambas se diferencian en el intervalo y el tipo de los
valores generados.
import matplotlib.pyplot as plt
%matplotlib inline

fig, (ax1, ax2) = plt.subplots(1,2, figsize= (12,3))


ax1.hist(np.random.rand(5000))
ax1.set_title('np.random.rand')

ax2.hist(np.random.randint(1, 10, size= 5000), bins = 9)


ax2.set_title('np.random.randint (1, 10)');

fig.savefig('./figuras/1_numpy_01.jpg')

En el caso de la función np.random.randn, podemos ver en la gura que los números generados siguen
una distribución normal centrada en el 0.

import matplotlib.pyplot as plt


%matplotlib inline

fig, ax3 = plt.subplots(1,1, figsize= (9,3))

ax3.hist(np.random.randn(10000))
ax3.set_title('np.random.randn')

fig.savefig('./figuras/1_numpy_02.jpg')

Lectura y escritura de arrays en Ficheros

Hasta el momento hemos podido ver diversas alternativas a la creación de arrays, pero no puede faltar
una de las más usadas cuando trabajamos con datos provenientes de fuentes externas y que suele ser el
punto de partida en el proceso de análisis de datos. Se trata de la creación de arrays mediante la lectura
de cheros.

La función np.load de NumPy permite crear arrays a partir de los datos almacenados en un chero en
formato binario:

a = np.load('./datos/pares.npy')
a

array([2, 4, 6, 8])

De forma análoga, la función np.save permite guardar los datos contenidos en un array en un chero en
formato bnario en disco. Estos cheros tienen extensión npy:

np.save('./datos/pares', a)

Para el caso de cheros de texto, NumPy proporciona funciones equivalentes np.load y np.save. Se
trata de las funciones np.loadtxt y np.savetxt. En el siguiente ejemplo mostramos cómo leer el
chero datos/temperaturas.dat y el chero datos/caract.csv.

c = np.loadtxt('./datos/temperaturas.dat')
c

array([ 3.1, 3.3, 6.2, 9.2, 13.1, 15.6, 17.9, 17.5, 14.5, 10.7, 6.7,
3.7])

d = np.loadtxt('./datos/caract.csv', delimiter=',')
d

array([[ 89.2, 3. ],
[ 88.3, 4. ],
[ 90. , 5. ],
[100. , 6. ],
[ 85.4, 7. ],
[103. , 8. ],
[ 91.2, 9. ]])

Estas funciones ofrecen muchas posibilidades, como por ejemplo usar diferentes delimitadores, aplicar
funciones de conversión en alguna de sus columnas, saltar las, omitir columnas, etc. En el capítulo
dedicado a Pandas veremos estas opciones con más detalle. En el siguiente ejemplo mostramos cómo
leer una sola columna del chero datos/caract.csv:

d = np.loadtxt('./datos/caract.csv', delimiter=',', usecols=[1])


d

array([3., 4., 5., 6., 7., 8., 9.])

En el siguiente ejemplo mostramos cómo guardar los datos de un array en un chero de texto. Por
ejemplo, aplicamos la función np.cumsum para crear un array con la suma acumulada del array d y
guardamos esos datos en un nuevo chero de texto.
suma_acumulada = np.cumsum(d)
suma_acumulada

array([ 3., 7., 12., 18., 25., 33., 42.])

np.savetxt("./datos/resultado.csv", suma_acumulada, delimiter=',')

La función np.genfromtxt tiene más funcionalidad que np.loadtxt para el caso de cheros en
formato CSV. Por ejemplo, permite leer la línea de cabecera si existe y acceder a los datos del array por el
nombre. Por ejemplo, para leer el chero ‘medidas.csv’ (ver Fichero datos/medidas.csv), usamos la
función np.genfromtxt usando como argumentos, el nombre del chero y el delimitador. Si el chero
trae cabeceras, lo indicamos mediante el argumento name con valor True.

mmm = np.genfromtxt('./datos/medidas.csv', delimiter=',', names = True)

mmm

array([(30. , 1.23), (33.2, 1.25), (46.3, 1.4 )],


dtype=[('Peso', '<f8'), ('Altura', '<f8')])

De esta forma, es posible acceder a los datos mediante el nombre de las columnas.

mmm['Altura']

array([1.23, 1.25, 1.4 ])

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial Garceta.
ISBN: 978-84-1622-883-6

https://docs.scipy.org/doc/numpy/reference/routines.html En este enlace podemos encontrar el


repertorio de funciones y métodos implementados en la librería NumPy. Todos ellos se encuentran
clasi cados por categorías. Además se presentan ejemplos de uso para facilitar su comprensión.

Lista completa de funciones universales https://docs.scipy.org/doc/numpy/reference/ufuncs.html

Libro del autor de NumPy: http://csc.ucdavis.edu/~chaos/courses/nlp/Software/NumPyBook.pdf


Sección 3.3

Acceso y recorrido de arrays

El acceso a los elementos (o subarrays) de un array se realiza de forma similar a como se hace en el caso
de listas, es decir, usamos la notación de corchetes. En general, la expresión entre corchetes es una tupla,
donde cada elemento de la tupla impone las condiciones de selección para cada una de las dimensiones
del array.

Arrays de una dimensión

En el caso de arrays de una dimensión, es posible seleccionar elementos individauales inidicando el


índice (comenzando desde el 0). Si queremos seleccionar una partición del array, la expresión entre
corchetes es de la forma m:n:p. Dicha expresión selecciona elementos entre las posiciones m y n-1 con
incremento p. Si p es un valor negativo, se seleccionan los elementos en orden inverso.

A continuación mostramos algunos ejemplos. Partimos de un array de una dimensión que contiene
elementos entre el 3 y el 11:

import numpy as np

arr = np.arange(3,12)
arr

array([ 3, 4, 5, 6, 7, 8, 9, 10, 11])

Para seleccionar el primer elemento, el tercero y el último, escribimos lo siguiente:

arr[0]

arr[2]

arr[-1]

11

Para seleccionar un rango de elementos, por ejemplo los elementos entre el segundo y quinto elemento,
o los tres últimos elementos, escribimos los siguiente:

arr[1:4]

array([4, 5, 6])

arr[-3:]

array([ 9, 10, 11])

Para seleccionar todos los elementos en órden inverso, o todos los elementos en orden inverso con
incremento 2, escribimos lo siguiente:

arr[::-1]

array([11, 10, 9, 8, 7, 6, 5, 4, 3])

arr[::-2]

array([11, 9, 7, 5, 3])

Arrays multidimensionales

El acceso a los elementos de un array multidimensional se realiza aplicando a cada una de sus
dimensiones, el procedimiento visto para arrays de una dimensión.

A continuación creamos un array bidimensinal y mostramos algunos ejemplos.

arr = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]], dtype = 'int')


arr

array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])

Para seleccionar la segunda la escribimos lo siguiente:

arr[1,:]

array([5, 6, 7, 8])

Para seleccionar la segunda columna:

arr[:,1]

array([ 2, 6, 10])

Para seleccionar el subarray resultado de eliminar la primera columna y la última la escribimos lo


siguiente:

arr[:2, 2:]

array([[3, 4],
[7, 8]])
Una diferencia importante con respecto a las listas, es que las particiones de un array en NumPy
mediante la notación m:n:p son vistas del array original. Todos los cambios realizados en las vistas,
afectan al array original. En el siguiente ejemplo creamos un array a mediante el rango de valores entre 1
y 10. Posteriormente creamos el array b a partir de a:

a = np.arange(1, 11)
a

array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

b = a[:4]
b

array([1, 2, 3, 4])

Los elementos en el array b no son una copia de los cuatro primeros elementos de a, sino que ambos
arrays hacen referencia a los mismos elementos en memoria, lo que implica que todos los cambios en b
afectan también al array a:

b[::] = 0
a

array([ 0, 0, 0, 0, 5, 6, 7, 8, 9, 10])

a[2] = 7
b

array([0, 0, 7, 0])

Hay que recordar que NumPy ha sido diseñado para manejar grandes cantidades de datos y este
comportamiento evita problemas de memoria. En cualquier caso, si es necesario realizar la copia de un
array o de partes de él, podemos usar la función np.copy:

c = np.copy(a[5:])
c

array([ 6, 7, 8, 9, 10])

En este caso, los cambios en el array c no tienen efecto en el array a:

c[:] = 1
c

array([1, 1, 1, 1, 1])

array([ 0, 0, 7, 0, 5, 6, 7, 8, 9, 10])

Selección mediante iltros


Otra forma de acceso a partes de un array es mediante otro array, una lista o una tupla de valores de tipo
entero que indícan las posiciones del array original a seleccionar. Este array se le conoce como ltro. Por
ejemplo, si queremos seleccionar los elementos en las posiciones 0, 5 y 7 del array a, escribimos lo
siguiente:

array([ 0, 0, 7, 0, 5, 6, 7, 8, 9, 10])

a[[0,5,7]]

array([0, 6, 8])

O lo que es lo mismo:

m = np.array([0,5,7])
a[m]

array([0, 6, 8])

En los ejemplos anteriores, tanto la lista [0,5,7] como el array m actúan como ltros para la selección de
elementos del array a.

Otra forma de seleccionar partes de un array es mediante el uso de arrays de elementos de tipo bool. En
este caso, cada elemento del array (con valores True o False) indica si el elemento del array original con
el mismo índice, se selecciona o no. Supongamos que queremos seleccionar los elementos ´mayores que
7 del array b, donde b se crea como un rango de valores entre el 0 y el 11:

b = np.arange(12)
b

array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])

La siguiente expresión aplica el operador de comparación > a cada uno de los elementos del array b,
produciendo como resultado un array de elementos de tipo bool. Este array es el que actuará como
ltro:

b > 7

array([False, False, False, False, False, False, False, False, True,


True, True, True])

Ahora, realizamos la selección de los elementos de b utilizando el array generado por la expresión b > 7:

b[b > 7]

array([ 8, 9, 10, 11])

Manipular la forma de un array

Cuando trabajamos con arrays de datos, puede ser útil modi car la forma de un array sin cambiar los
datos que contiene. Por ejemplo, un array de 2 las y 2 columnas se puede transformar en un array
unidimensional de 4 elementos. En NumPy, la función np.reshape permite cambiar las dimensiones de
un array sin modi car los datos que contiene. Veamos un ejemplo:

arr = np.array([[2,4,6],[1,2,3]], dtype = 'int')


arr

array([[2, 4, 6],
[1, 2, 3]])

np.reshape(arr, 6) # función reshape definida en el módulo Numpy

array([2, 4, 6, 1, 2, 3])

Esta operación también se encuentra de nida como una operación de los arrays, por lo que también
puede invocarse sobre una variable de tipo array sando la notación punto ., seguido del nombre de la
operación:

arr.reshape(6) # operación (o método) reshape definida para ser ejecutada sobre un array

array([2, 4, 6, 1, 2, 3])

La función np.reshape recibe como argumentos un array y la nueva dimensión. Por supuesto, es
necesario que la dimensión del array sea compatible con el número de elementos del array.

np.reshape(arr,(3,2))

array([[2, 4],
[6, 1],
[2, 3]])

Un caso particular de la función np.reshape es la función np.ravel (y la correspondiente operación


ravel de los arrays) que premite transformar un array multidimensional en un array de una dimensión
con tantos elementos como el array original.

m = np.array([[2,4,6],[1,2,3]], dtype = 'int')


m

array([[2, 4, 6],
[1, 2, 3]])

m.ravel() # operación (o método) ravel definida para ser ejecutada sobre un array

array([2, 4, 6, 1, 2, 3])

np.ravel(arr) # función ravel definida en el módulo Numpy

array([2, 4, 6, 1, 2, 3])

Hay que tener en cuenta que las funciones np.reshape y np.ravel devuelven como resultado una vista
del array original, mientras que la función np.flatten devuelve una copia.

t = m.flatten()
t

array([2, 4, 6, 1, 2, 3])
En el caso de necesitar construir un array a partir de otros arrays existentes, NumPy proporciona las
funciones np.vstack y np.hstack. La función np.vstack permite concatenar una secuencia de arrays
verticalmente, mientras que la función np.vstack lo hace horizontalmente. Para que ambas operaciones
tengan éxito, las dimensiones de los arrays involucrados han de ser compatibles. Veamos algunos
ejemplos:

a = np.random.randint(0,9, (2,4))
a

array([[8, 4, 0, 0],
[8, 3, 8, 8]])

b = np.random.randint(0,2 ,(2,2))
b

array([[1, 0],
[1, 0]])

c = np.random.randint(5,9 ,(2,2))
c

array([[5, 7],
[7, 7]])

Los arrays a y b pueden concatenarse horizontalmente pero no verticalmente, mientras que los arrays b
y c pueden concatenarse verticalmente pero no horizontalmente.

np.hstack((a,b))

array([[8, 4, 0, 0, 1, 0],
[8, 3, 8, 8, 1, 0]])

np.vstack((b,c))

array([[1, 0],
[1, 0],
[5, 7],
[7, 7]])

El número de elementos del array no puede cambiar una vez ha sido creado. Para insertar y eliminar
elementos de un array, NumPy proporciona las funciones np.insert, np.append y np.delete. Todas
ellas devuelven una copia del array original.

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial Garceta.
ISBN: 978-84-1622-883-6

Libro del autor de NumPy: http://csc.ucdavis.edu/~chaos/courses/nlp/Software/NumPyBook.pdf


Sección 3.4

Operaciones vectorizadas

Los arrays realizan una gestión de la memoria mucho más e ciente que las listas y por tanto se mejora el
rendimiento. NumPy implementa una gran variedad de operaciones y funciones matemáticas mediante
expresiones sencillas sobre arrays (expresiones vectorizadas). El uso de estas expresiones permite operar
sobre los elementos del array sin necesidad de escribir múltiples y costosos loops.

Operaciones Aritméticas

Los operadores aritméticos sobre arrays, son operadores que se aplican elemento a elemento. El
resultado es un nuevo array cuyos datos depende de la operación realizada. Veamos algunos ejemplos:

import numpy as np

a = np.array([1,2,3], int)
b = np.array([5,2,6], int)

a + b

array([6, 4, 9])

a - b

array([-4, 0, -3])

a * b

array([ 5, 4, 18])

En el caso de operaciones entre arrays y escalares, el escalar se aplica a cada uno de los elementos del
array:

a * 10

array([10, 20, 30])

b ** 2

array([25, 4, 36], dtype=int32)


En el caso de arrays multidimensionales, se sigue manteniendo que las operaciones se realizan elemento
a elemento. Por ejemplo en el caso de dos dimensiones, el producto de dos arrays NO se corresponde
con la multiplicación de matrices según la conocemos.

a = np.array([[1,2], [3,4]], int)


a

array([[1, 2],
[3, 4]])

b = np.array([[2,0], [1,3]], int)


b

array([[2, 0],
[1, 3]])

a * b

array([[ 2, 0],
[ 3, 12]])

a - b

array([[-1, 2],
[ 2, 1]])

En el caso de las operaciones entre arrays, NumPy comprueba la compatibilidad de las dimensiones
antes de realizar la operación. Si los arrays involucrados tienen los mismos valores para todas sus
dimensiones, la operación se realiza sin problemas. En caso contrario, NumPy comprueba que el valor
sea 1 para todas las dimensiones con valores distintos. Porteriormente se extienden los arrays hasta
conseguir que los arrays tengan los mismos valores en todas sus dimensiones y nalmente realiza la
operación. Esta operación de extensión de arrays se la conoce como broadcasting.
Por ejemplo, en el caso de arrays bidimensionales, el array m y n son compatibles y se pueden sumar:

m = np.array([[1, 2], [3,4]], int)


m.shape

(2, 2)

n = np.array([[2],[3]], int)
n.shape

(2, 1)

m + n

array([[3, 4],
[6, 7]])

En el ejemplo anterior se puede ver cómo la operación suma tiene éxito con los arrays m y n con
dimensiones (2, 2) y (2, 1) respectivamente. La siguiente gura muestra cómo se realiza la operación de
extensión de dimensiones (conocida como broadcasting) antes de realizar la suma de los arrays.
La segunda dimensión del array m tiene valor 2, mientras que la segunda dmensión del array n es 1. El
valor es distinto para la segunda dimensión, pero la operación se puede realizar extendiendo
previamente la segunda dimensión del array n.

En el siguiente ejemplo, mostramos la suma de n1 y n2 con dimensiones (2,1) y (1,2) respectivamente.


NumPy extiende ambos arrays hasta alcanzar dimensión (2,2) antes de realizar la operación.

n1 = np.array([[2],[3]], int)
n1.shape

(2, 1)

n2 = np.array([[2, 3]], int)


n2.shape

(1, 2)

n1 + n2

array([[4, 5],
[5, 6]])

Puede darse el caso de que los arrays no tengan ni siquiera el mismo número de dimensiones. En ese
caso NumPy asigna valor 1 a las dimensiones no existentes.
m1 = np.array([[1, 2], [3,4]], int)
m1

array([[1, 2],
[3, 4]])

m1.shape

(2, 2)

m2 = np.array([5, 2], int)


m2

array([5, 2])

m1.shape

(2, 2)

m1 * m2

array([[ 5, 4],
[15, 8]])

El producto matricial se obtiene mediante el uso de la función np.dot o creando objetos de tipo matrix
en lugar de ndarray.

A = np.array( [[1,1],[0,1]] )
B = np.array( [[2,0],[3,4]] )
M = np.dot(A,B)
M

array([[5, 4],
[3, 4]])

Funciones Universales

Se trata de funciones que se aplican a cada uno de los elementos de un array. Cada una de estas
funciones recibe un array de entrada y devuelve como resultado otro array de la misma forma. Cada uno
de los elementos del array devuelto, es el resultado de aplicar la función al correspondiente elemento en
el array de entrada. Muchas de estas funciones son de aridad 1, como np.sqrt o np.cos:

a = np.array([0, 1, 4, 9, 16])
a

array([ 0, 1, 4, 9, 16])

Para calcular la raiz cuadrada de cada uno de los elementos del array escribimos lo siguiente:

np.sqrt(a)

array([0., 1., 2., 3., 4.])

Para calcular el coseno de cada uno de los elementos del array escribimos lo siguiente:
np.cos(a)

array([ 1. , 0.54030231, -0.65364362, -0.91113026, -0.95765948])

El cálculo del cuadrado de cada elemento:

np.square(a)

array([ 0, 1, 16, 81, 256], dtype=int32)

Otras funciones son de aridad 2, como por ejemplo np.maximum, np.power o np.greater_equal:

b = np.array([1, 20, 3, 40, 5])

np.maximum(a,b)

array([ 1, 20, 4, 40, 16])

np.power(b,3)

array([ 1, 8000, 27, 64000, 125], dtype=int32)

np.greater_equal(a,b)

array([False, False, True, False, True])

A continuación mostramos algunas de las funciones universales disponibles en NumPy.

Funciones trigonométricas

np.sin, np.cos, np.tan


np.arcsin, np.arccos, np.arctan
np.sinh, np.cosh, np.tanh
np.arcsinh, np.arccosh, np.arctanh
np.sin, np.cos, np.tan
np.arcsin, np.arccos, np.arctan
np.sinh, np.cosh, np.tanh
np.arcsinh, np.arccosh, np.arctanh

Funciones logarítmicas

np.log, np.log10, np.log2

Raíz cuadrada, potencia y exponente

np.sqrt
np.exp
np.power

Suma, resta, multiplicación y división


np.add, np.subtract
np.multiply, np.divide

Resto de la división entera (operación módulo)

np.remainder

Signo y valor absoluto

np.sign, np.abs

Conversión a valores de tipo entero

np.floor, np.ceil, np.rint

En https://docs.scipy.org/doc/numpy/reference/ufuncs.html se puede encontrar la lista completa de


funciones universales.

Funciones de Agregación

Las funciones de agregación en NumPy reciben como argumento de entrada un array y devuelven como
resultado un valor escalar. El cálculo del máximo valor de un array np.max, la suma de sus elementos
np.sum o las funciones estadísticas como por ejemplo la media np.meano la desviación típica np.std
son ejemplos de funciones de agregación. Todas ellas también se encuentran disponibles como
operaciones de los arrays.

datos = np.arange(1, 10)


datos

array([1, 2, 3, 4, 5, 6, 7, 8, 9])

np.mean(datos) # operación definida en el módulo


5.0

O lo que es lo mismo:

datos.mean() # operación definida en el tipo de datos ndarray

5.0

datos.prod()

362880

np.remainder(datos,2)

array([1, 0, 1, 0, 1, 0, 1, 0, 1], dtype=int32)

A continuación mostramos algunas de las funciones de agregación disponibles en NumPy.


np.mean Calcula la media de los elementos del array
np.var Calcula la varianza de los elementos del array
np.std Calcula la desviación típica
np.prod Calcula el producto de los elementos del array
np.sum Calcula la suma de los elementos del array
np.min Devuelve el mínimo elemento del array
np.max Devuelve el máximo elemento del array
np.argmin Devuelve el índice asociado al mínimo elemento del array
np.argmax Devuelve el índice asociado al máximo elemento del array
np.cumsum Devuelve la suma acumulada de los elementos del array
np.cumprod Devuelve el producto acumulado de los elementos del array

Por defecto, las funciones de agregación se aplican sobre el conjunto de todos los elementos del array de
entrada. Es posible indicar mediante el argumento axis, el eje sobre el cuál la función de agregación
debe aplicarse. Por ejemplo, en el caso de arrays de dos dimensiones, podemos calcular la suma
completa de los elementos del array o la suma de los elementos con respecto al eje 0 o el eje 1.

datos = np.arange(1,10)
datos = datos.reshape(3,3)
datos

array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

datos.sum()

45

datos.sum(axis = 0)

array([12, 15, 18])

datos.sum(axis = 1)

array([ 6, 15, 24])

Operaciones con Arrays de Tipo bool

Las operaciones de comparación de dos array devuelven arrays de tipo bool. NumPy proporciona
funciones capaces de operar con los resultados de las operaciones de comparación entre arrays.
Ejemplos de estas funciones son np.all y np.any:

x = np.array([1, 2, 3, 4, 5])
y = np.array([5, 9, 8, 1, 7])
x < y

array([ True, True, True, False, True])

El resultado de la comparación es un array de elementos de tipo bool. La función np.all devuelve valor
True si todos los elementos en x < y son True.

np.all( x < y )

False

La función np.any devuelve valor True si alguno de los elementos en x < y es True.

np.any( x < y )

True

La ventaja del uso de estas funciones es que en la mayoría de los casos permiten prescindir de
instrucciones condicionales del tipo if ... then ...

Expresiones Condicionales

La función np.where es la versión vectorizada de la expresión ternaria x if cond else y.


Supongamos que tenemos los tres arrays:

x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
cond = np.array([True, False, False, True])

La función np.where selecciona elementos de dos arrays (segundo y tercer argumento) dependiendo del
valor de un array de tipo bool (primer argumento). Si la condición es cierta, el resultado es el elemento en
el array del segundo argumento. Si la condición es falsa, el resultado es el elemento en el array del tercer
argumento.

np.where(cond, x, y)

array([1, 6, 7, 4])

En realidad, los dos últimos argumentos de la operación np.where no tienen por qué ser arrays, pueden
ser escalares.

np.where(cond, x, 0)

array([1, 0, 0, 4])

Ejemplo:

Supongamos que queremos sustituir los valores negativos de un array por 0. Los valores positivos no se
ven modi cados.
a = np.array([[4, 5, -1], [8, 6, -3], [-5, -4, 3]])
a

array([[ 4, 5, -1],
[ 8, 6, -3],
[-5, -4, 3]])

np.where(a >0 , a, 0)

array([[4, 5, 0],
[8, 6, 0],
[0, 0, 3]])

La condición a > 0 se evalúa para cada elemento del array a (recordar que las operaciones son
vectorizadas). Si la condición es cierta, la función where devuelve ese elemento. En caso contrario
devuelve 0.

Ejemplo:

Supongamos que queremos transformar los valores negativos en positivos. Los valores positivos no se
ven modi cados.

array([[ 4, 5, -1],
[ 8, 6, -3],
[-5, -4, 3]])

np.where(a < 0 , abs(a), a)

array([[4, 5, 1],
[8, 6, 3],
[5, 4, 3]])

La operación anterior también se podría obtener usando la función np.abs de la librería NumPy.

array([[ 4, 5, -1],
[ 8, 6, -3],
[-5, -4, 3]])

np.abs(a)

array([[4, 5, 1],
[8, 6, 3],
[5, 4, 3]])

Operaciones de Conjunto

La siguiente tabla describe algunas de las operaciones de conjunto más usadas en el caso de arrays de
una dimensión. El uso de arrays para describir conjuntos de datos y operar sobre ellos, permite escribir
ciertas operaciones mediante expresiones vectorizadas.
Función Descripción

np.unique Crea un nuevo array con elementos sin repetición

np.in1d Comprueba si los elementos de un array están en otro array

np.union1d Crea un nuevo array cuyos elementos están en uno de los dos arrays o en ambos

np.intersect1d Crea u nuevo array cuyos elementos están en los dos arrays

Crea un nuevo array cuyos elementos están en el primer array pero no en el


np.diff1d
segundo

La función np.unique devuelve un array ordenado sin elementos repetidos.

a = np.array([1, 5 ,6 ,2, 4, 1, 4, 5, 3, 1, 2 , 2])


np.unique(a)

array([1, 2, 3, 4, 5, 6])

Por otro lado, la función np.in1d comprueba la pertenencia de los elementos de un array en otro array.
Esta operación se realiza elemento a elemento y devuelve como resultado un array de tipo bool.

b = np.array([6, 1, 0, 5 ])
np.in1d(b, a)

array([ True, True, False, True])

Para comprobar si b es un subconjunto de a podemos combinar las operaciones np.in1d y np.all de la


siguiente manera:

np.all(np.in1d(b,a))

False

La operaciones union, intersección y diferencia se representan mediante las funciones np.union1d,


np.intersect1d y np.setdiff1d respectivamente:

np.union1d(a,b)

array([0, 1, 2, 3, 4, 5, 6])

np.intersect1d(a,b)

array([1, 5, 6])

Orden

Al igual que las listas, es posible ordenar arrays mediante la función np.sort o la operación sort de los
arrays. La función recibe como argumento un array y devuelve un nuevo array ordenado. Sin embargo, la
operación sort no devuelve un nuevo array, si no que modi ca el array a ordenar.
arr = np.random.randint(1,10, 6)
arr

array([2, 4, 6, 2, 9, 3])

np.sort(arr)

array([2, 2, 3, 4, 6, 9])

arr

array([2, 4, 6, 2, 9, 3])

arr.sort()

arr

array([2, 2, 3, 4, 6, 9])

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Libro del autor de NumPy: http://csc.ucdavis.edu/~chaos/courses/nlp/Software/NumPyBook.pdf
https://docs.scipy.org/doc/numpy/reference/routines.html En este enlace podemos encontrar el
repertorio de funciones y métodos implementados en la librería NumPy. Todos ellos se
encuentran clasi cados por categorías. Además se presentan ejemplos de uso para facilitar su
comprensión.
Lista completa de funciones universales https://docs.scipy.org/doc/numpy/reference/ufuncs.html
Sección 3.5

Ejemplo 1

Tanto los arrays de NumPy como las listas de Python, sirven para almacenar colecciones de datos. La
principal diferencia entre ambas estructuras de datos radica en las operaciones que se realizan sobre
ellas.

A continuación mostramos, mediante un ejemplo, la potencia y la e ciencia de las operaciones sobre


arrays usando la librería NumPy frente a las operacines sobre listas.

En primer lugar, creamos una lista y un array de NumPy con las funciones range y np.arange
respectivamente.

import numpy as np

lista = range(1000)
arr = np.arange(1000)

Supongamos que queremos calcular la suma acumulada de una colección de elementos. La función
sumaAcumulada1 recibe como argumento una lista y resuelve el problema iterando sobre cada uno de
los elementos de las lista usando el iterador for.

def sumaAcumulada1(l):
r = [l[0]]
for i in range(1,len(l)):
r.append(r[i-1]+l[i])
return r

Por otro lado, la función sumaAcumulada2 recibe como argumento un array de NumPy y devuelve como
resultado la suma acumulada de los elementos del array usando la función np.cumsum de la librería
NumPy.

def sumaAcumulada2(a):
return np.cumsum(a)

Como se puede observar, la función sumaAcumulada2 no necesita iterar sobre los elementos del array
mediante la instrucción for. A continuación mostramos los tiempos que consumen ambas funciones en
ejecutarse. Usamos el comando mágico %timeit.
%timeit r1 = sumaAcumulada1(lista)

246 µs ± 9.77 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit r2 = sumaAcumulada2(arr)

4.29 µs ± 158 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

La función sumaAcumulada2 es mucho más rápida que la función equivalente sumaAcumulada1.


Estamos hablando de 4.29 µs frente a 246 µs.
Sección 3.6

Ejemplo 2

Los datos contenidos en el chero datos.txt describe las poblaciones de tres tipos de serpientes en el sur
de Africa durante 20 años.

import numpy as np
data = np.loadtxt('./datos.txt') # carga de los datos
data

array([[ 2000., 30000., 3000., 38300.],


[ 2001., 37200., 6100., 48200.],
[ 2002., 60200., 9800., 31600.],
[ 2003., 77300., 36200., 38200.],
[ 2004., 36300., 69300., 30600.],
[ 2005., 20600., 31700., 49800.],
[ 2006., 18100., 19000., 38600.],
[ 2007., 21300., 13000., 32300.],
[ 2008., 22000., 8300., 43600.],
[ 2009., 26300., 9100., 32100.],
[ 2010., 27100., 7300., 36000.],
[ 2011., 30300., 8000., 36800.],
[ 2012., 67000., 12300., 33800.],
[ 2013., 76600., 19600., 30900.],
[ 2014., 62300., 36700., 49300.],
[ 2015., 19600., 61100., 39000.],
[ 2016., 11200., 29700., 36700.],
[ 2017., 7600., 16800., 41800.],
[ 2018., 13600., 9700., 33300.],
[ 2019., 16200., 10100., 31300.],
[ 2020., 23700., 8600., 47300.]])

La primera columna representa el año. La segunda columna representa la población de la primera


especie. La tercera y cuarta columnas representan las poblaciones de las especies tercera y cuarta
respectivamente. Como podemos observar, los datos en el array data son de tipo float. En este caso,
podemos indicar en la operación de carga el tipo de los datos. Por ejemplo, podemos indicar que el tipo
es int. Para ello usaremos el argumento dtype.
data = np.loadtxt('./datos.txt', dtype= 'int') # carga de los datos (indicamos el tipo `int`)
data

array([[ 2000, 30000, 3000, 38300],


[ 2001, 37200, 6100, 48200],
[ 2002, 60200, 9800, 31600],
[ 2003, 77300, 36200, 38200],
[ 2004, 36300, 69300, 30600],
[ 2005, 20600, 31700, 49800],
[ 2006, 18100, 19000, 38600],
[ 2007, 21300, 13000, 32300],
[ 2008, 22000, 8300, 43600],
[ 2009, 26300, 9100, 32100],
[ 2010, 27100, 7300, 36000],
[ 2011, 30300, 8000, 36800],
[ 2012, 67000, 12300, 33800],
[ 2013, 76600, 19600, 30900],
[ 2014, 62300, 36700, 49300],
[ 2015, 19600, 61100, 39000],
[ 2016, 11200, 29700, 36700],
[ 2017, 7600, 16800, 41800],
[ 2018, 13600, 9700, 33300],
[ 2019, 16200, 10100, 31300],
[ 2020, 23700, 8600, 47300]])

Cálculo de la media de población de cada tipo a lo largo del tiempo.

poblaciones = data[:, 1:] # nos quedamos con las tres últimas columnas
poblaciones

array([[30000, 3000, 38300],


[37200, 6100, 48200],
[60200, 9800, 31600],
[77300, 36200, 38200],
[36300, 69300, 30600],
[20600, 31700, 49800],
[18100, 19000, 38600],
[21300, 13000, 32300],
[22000, 8300, 43600],
[26300, 9100, 32100],
[27100, 7300, 36000],
[30300, 8000, 36800],
[67000, 12300, 33800],
[76600, 19600, 30900],
[62300, 36700, 49300],
[19600, 61100, 39000],
[11200, 29700, 36700],
[ 7600, 16800, 41800],
[13600, 9700, 33300],
[16200, 10100, 31300],
[23700, 8600, 47300]])

poblaciones.mean(axis = 0) # media de las filas (axis = 0) para cada una de las columnas

array([33547.61904762, 20257.14285714, 38071.42857143])

Mostrar los resultados expresado en miles de ejemplares. Tendremos que dividir por mil el
array.
poblaciones = poblaciones / 1000
poblaciones

array([[30. , 3. , 38.3],
[37.2, 6.1, 48.2],
[60.2, 9.8, 31.6],
[77.3, 36.2, 38.2],
[36.3, 69.3, 30.6],
[20.6, 31.7, 49.8],
[18.1, 19. , 38.6],
[21.3, 13. , 32.3],
[22. , 8.3, 43.6],
[26.3, 9.1, 32.1],
[27.1, 7.3, 36. ],
[30.3, 8. , 36.8],
[67. , 12.3, 33.8],
[76.6, 19.6, 30.9],
[62.3, 36.7, 49.3],
[19.6, 61.1, 39. ],
[11.2, 29.7, 36.7],
[ 7.6, 16.8, 41.8],
[13.6, 9.7, 33.3],
[16.2, 10.1, 31.3],
[23.7, 8.6, 47.3]])

Redondear los valores al entero más cercano.

poblaciones = np.around(poblaciones)
poblaciones

array([[30., 3., 38.],


[37., 6., 48.],
[60., 10., 32.],
[77., 36., 38.],
[36., 69., 31.],
[21., 32., 50.],
[18., 19., 39.],
[21., 13., 32.],
[22., 8., 44.],
[26., 9., 32.],
[27., 7., 36.],
[30., 8., 37.],
[67., 12., 34.],
[77., 20., 31.],
[62., 37., 49.],
[20., 61., 39.],
[11., 30., 37.],
[ 8., 17., 42.],
[14., 10., 33.],
[16., 10., 31.],
[24., 9., 47.]])

Cálculo de la desviación estándar de la muestra

poblaciones.std(axis=0)

array([21.11559804, 17.56116619, 6.17911045])

¿Qué especie ha tenido más población por año?

# máximo de cada año (máximo de las columnas (axis = 1) para cada una de las filas)

maximo= poblaciones.max(axis = 1)
maximo

array([38., 48., 60., 77., 69., 50., 39., 32., 44., 32., 36., 37., 67.,
77., 62., 61., 37., 42., 33., 31., 47.])
especie = np.argmax(poblaciones, axis=1) # posición que ocupa el máximo valor
especie
# columna 0 se refiere a la especie 1
# columns 1 se refiere a la especie 2
# columns 2 se refiere a la especie 3
array([2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 2, 2],
dtype=int64)

Para mostrar los resultados por años de forma más clara:

# creamos un diccionario
nombres = {0 : 'Especie 1 ', 1: 'Especie 2', 2: 'Especie 3'}

for fila, dato in enumerate(especie):


print (int(data[fila, 0]), '-' , nombres[dato], '-', maximo[fila])

2000 - Especie 3 - 38.0


2001 - Especie 3 - 48.0
2002 - Especie 1 - 60.0
2003 - Especie 1 - 77.0
2004 - Especie 2 - 69.0
2005 - Especie 3 - 50.0
2006 - Especie 3 - 39.0
2007 - Especie 3 - 32.0
2008 - Especie 3 - 44.0
2009 - Especie 3 - 32.0
2010 - Especie 3 - 36.0
2011 - Especie 3 - 37.0
2012 - Especie 1 - 67.0
2013 - Especie 1 - 77.0
2014 - Especie 1 - 62.0
2015 - Especie 2 - 61.0
2016 - Especie 3 - 37.0
2017 - Especie 3 - 42.0
2018 - Especie 3 - 33.0
2019 - Especie 3 - 31.0
2020 - Especie 3 - 47.0
Sección 3.7

Ejercicios

Los enlaces que te encuentres en esta página solo funcionan desde el formato html, no desde el
notebook una vez descargado.

Ejercicio 1

Disponemos de dos cheros que contienen mediciones de temperatura y humedad relativa del aire en
diferentes puntos de una zona geográ ca. Si observamos los datos, se trata de una matriz de celdas (o
píxeles) organizadas en las y columnas (o una cuadrícula) en la que cada celda contiene un valor que
representa cierta información. En este caso, la temperatura y la humedad relativa.

Las mediciones de temperatura de nuestra zona geográ ca de estudio se encuentran en el


chero temperaturas.txt
El chero humedad.txt recoge la humedad relativa de los mismos puntos en la misma zona
geográ ca.

a) Crear un array de numpy con los datos de las temperaturas de la zona geográ ca de estudio. El array
ha de llamarse temperatura.

Descarga el chero en tu directorio de trabajo.


Carga el chero mediante la función loadtxt. Usa el parámetro dtype para indicar que los
valores sean de tipo int. Si tu sistema operativo es iOS, es probable que te de error si pones tipo
int. Prueba a poner float.

# Sol:

b) Crear un array de numpy con los datos de las mediciones de humedad de la zona geográ ca de
estudio. El array ha de llamarse humedad.

Descarga el chero en tu directorio de trabajo.


Carga el chero mediante la función loadtxt. Usa el parámetro dtype para indicar que los
valores sean de tipo float.
# Sol:

c) Determinar el tamaño de los datos. ¿Cuántas las y cuántas columnas tiene el array temperatura?,
¿Cuantas las y cuántas columnas tiene el array humedad?

# sol:
# Ambos arrays son del mismo tamaño, 133 filas y 133 columnas

d) Calcula la temperatura máxima y mínima registrada.

# SOl:
# temperatura máxima: 31
# temperatura máxima: 10

e) El índice de temperatura-humedad, es un número utilizado para indicar la falta de confort causada por
los efectos combinados de la temperatura y la humedad del aire. El índice fue desarrollado en 1959 por
Earl C. Thom, un investigador de la O cina de Climatología del US Weather Bureau. La fórmula para
calcular el índice de temperatura-humedad (ITH) es:

𝐼𝑇𝐻 𝑡𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑎
= 0.8 × +
ℎ𝑢𝑚𝑒𝑑𝑎𝑑𝑟𝑒𝑙𝑎𝑡𝑖𝑣𝑎
100
×(𝑡𝑒𝑚𝑝𝑒𝑟𝑎𝑡𝑢𝑟𝑎 − 14.3) + 46.4

Calcula el índice de temperatura-humedad (ITH) de cada una de las celdas de la cuadrícula. El resultado
debe ser un array bidimensional con el mismo número de las y columnas que los arrays temperatura y
humedad. El nombre de este array debe ser ith.

Para calcular el índice de temperatura humedad puedes seguir los siguientes pasos:

(1) Multiplica el array temperatura por 0.8


(2) Divide el array humedad por 100
(3) Resta 14.3 al array temperatura
(4) Multiplica el resultado de (2) por el resultado de (3)
(5) Suma 46.4 al resultado de (4)
(6) Suma el resultado de (5) y el resultado de (1)

# Sol:

# ith = array([[60.3254, 60.3237, 58.8655, ..., 63.9043, 63.9006, 63.9006],


# [63.8969, 63.8821, 63.8858, ..., 70.006 , 68.426 , 68.4193],
# [69.9983, 69.9983, 69.9906, ..., 63.5364, 63.5364, 63.5129],
# ...,
# [62.3517, 62.3517, 62.3544, ..., 64.9558, 64.9511, 64.9464],
# [64.9511, 64.9464, 63.4196, ..., 64.326 , 64.3495, 64.3636],
# [64.3636, 65.7459, 65.6604, ..., 63.6962, 63.6962, 63.7103]])

f) Redondea los valores del array obtenido en el apartado anterior (ith) al entero más cercano. Puedes
usar la función universal np.around. Para obtener información de uso de la función np.around ejecuta
la siguiente celda o consulta la ayuda en
https://numpy.org/doc/stable/reference/generated/numpy.around.html
# ejecuta esta celda y busca el ejemplo de uso
np.around?

# Sol:
# ith = array([[60., 60., 59., ..., 64., 64., 64.],
# [64., 64., 64., ..., 70., 68., 68.],
# [70., 70., 70., ..., 64., 64., 64.],
# ...,
# [62., 62., 62., ..., 65., 65., 65.],
# [65., 65., 63., ..., 64., 64., 64.],
# [64., 66., 66., ..., 64., 64., 64.]])

g) Calcula la media del índice de temperatura-humedad (ITH).

# Sol:

h) En la actualidad este índice se utiliza para supervisar las condiciones de estrés por calor a las que está
expuesto el ganado bovino, y su correlato en la producción lechera y su e ciencia reproductiva. En
general los expertos han determinado que con un índice ITH inferior a 72 el ganado lechero no
experimenta estrés, si el IHT se entre 72 y 78 el estrés es moderado y valores ITH de entre 78 y 88 se
caracterizan por un estrés grave.

Determina si se produce estrés en algún punto de la cuadrícula, es decir, si existe algún valor mayor a 72.
¿Se produce estrés grave en algún punto?

# Sol:
Table of Contents

1 Las Series y DataFrames de Pandas


1.1 Importar librerías
1.2 Estructuras de Datos en Pandas
1.2.1 Series
1.2.2 Los datos de tipo DataFrame
1.3 Referencias

Sección 4.1

Las Series y DataFrames de Pandas

La librería Pandas proporciona estructuras de datos de alto nivel que permiten representar series y
tablas de datos, así como herramientas diseñadas especí camente para un tratamiento de datos rápido y
sencillo. Está construida sobre los fundamentos de NumPy, adaptando las operaciones sobre arrays a las
nuevas estructuras de nidas en Pandas. Resulta especialmente útil cuando hay que trabajar con datos
heterogéneos representados de forma tabular.

Empezó a desarrollarse en el año 2008 por Wes McKinney (McKinney, 2012) en un intento por conseguir
una herramienta exible y de alto rendimiento para el tratamiento de datos nancieros. En el año 2012,
Chang She comenzó a participar en el desarrollo de la librería. Actualmente Pandas forma parte de un
proyecto en el que participan muchos desarrolladores.

Pandas es actualmente una de las librerías de alto nivel de Python más utilizadas en las primeras etapas
del proceso de análisis de datos, especialmente apropiada en aplicaciones estadísticas.

En esta Sección introducimos las estructuras principales de Pandas, como son las Series y DataFrames,
así como las operaciones básicas que se pueden realizar con dichas estructuras.

Toda la información o cial de la librería se encuentra accesible en el enlace http://pandas.pydata.org.

Importar librerías
En esta Sección trabajaremos principalmente con las librerías Pandas, NumPy y la librería básica para
visualización Matplotlib. Importamos las tres librerías con alias pd, np y plt respectivamente:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

Estructuras de Datos en Pandas

En Pandas disponemos de varias estructuras de datos y operaciones (métodos) que nos ayudarán a
manipular dichas estructuras de forma sencilla y e ciente. Las dos estructuras de datos principales son
las Series y los DataFrames, capaces de representar series de datos y datos en forma tabular
respectivamente.

Series

Una variable del tipo Series puede verse como una variable de tipo array de una dimensión que
contiene datos de un cierto tipo y tiene asociado un índice. Este índice es un array de etiquetas cuyo
tamaño coincide con el tamaño del array de datos.

A continuación creamos una variable s de tipo Serie mediante la función pd.Series y una lista de
elementos:

s = pd.Series([10, 20, 30, 40, -10])


s

0 10
1 20
2 30
3 40
4 -10
dtype: int64

La función pd.Series es la constructora de series. Como podemos observar, la serie s contiene


elementos de tipo int64 (columna de la derecha). Los elementos de la serie están indexados mediante
un array de enteros comenzando desde el 0 (columna de la izquierda). Tal como ocurría con los arrays de
NumPy, las series tienen un conjunto de atributos o propiedades. Entre las propiedades de las Series
tenemos index y values, que representan el índice y los elementos de la serie:

s.index

RangeIndex(start=0, stop=5, step=1)

s.values

array([ 10, 20, 30, 40, -10], dtype=int64)

Una de las ventajas de trabajar con Series, es que es posible indexar cada elemento de la serie con un
valor descriptivo o etiqueta. Por ejemplo, si nuestros datos representan la tasa de natalidad de distintas
ciudades españolas, sería interesante usar el nombre de las ciudades cómo etiquetas del índice, en lugar
de usar etiquetas de tipo int:

nat = pd.Series([10.7, 7.5, 7.1, 17.8, 7.9 ],


index=['Murcia','Cantabria', 'Galicia', 'Melilla','Canarias' ],
name = 'Tasa de Natalidad')

nat

Murcia 10.7
Cantabria 7.5
Galicia 7.1
Melilla 17.8
Canarias 7.9
Name: Tasa de Natalidad, dtype: float64

El argumento name de la función pd.Series permite asignar un nombre a la serie creada. Otra de las
propiedades de las Series es name:

nat.name

'Tasa de Natalidad'

En este caso, tenemos dos procedimientos para acceder a los datos de una serie. El primero es mediante
la etiqueta del índice y notación de corchetes, como en el caso de las listas o los arrays:

nat['Murcia']

10.7

El segundo es mediante la etiqueta del índice (siempre que sea un nombre válido en Python) como si
fuera una propiedad de la serie:

nat.Murcia

10.7

Está claro que los datos los podríamos haber representado con un array en lugar de una serie, pero como
se puede ver, las series proporcionan mayor claridad en cuanto a los datos que contienen, la forma de
acceso, etc. Por ejemplo, es posible preguntar de forma natural si un determinado valor está en el índice:

'Cantabria' in nat.index

True

En cuanto a las operaciones con series, se mantienen muchas de las propiedades vistas para los arrays
de NumPy. Por ejemplo, las operaciones aritméticas (+, - , * , /) son vectorizadas, manteniéndose el valor
del índice de la serie:
nat * 100

Murcia 1070.0
Cantabria 750.0
Galicia 710.0
Melilla 1780.0
Canarias 790.0
Name: Tasa de Natalidad, dtype: float64

Las funciones de nidas en la librería Numpy pueden ser utilizadas con series de Pandas. El argumento de
la función puede ser una serie o un array.

nat

Murcia 10.7
Cantabria 7.5
Galicia 7.1
Melilla 17.8
Canarias 7.9
Name: Tasa de Natalidad, dtype: float64

np.ceil(nat)

Murcia 11.0
Cantabria 8.0
Galicia 8.0
Melilla 18.0
Canarias 8.0
Name: Tasa de Natalidad, dtype: float64

Otra de las operaciones que se aplican a los arrays de Numpy y que se extienden a las series de Pandas,
es la selección de elementos usando arrays de tipo bool.

nat[nat < 10]

Cantabria 7.5
Galicia 7.1
Canarias 7.9
Name: Tasa de Natalidad, dtype: float64

La característica estrella de las series es el alineamiento de datos en base al valor de los índices en las
operaciones entre dos series:

nach = pd.Series([2154, 8184, 10032, 780, 8177],


index=['Cantabria', 'Canarias',
'Galicia', 'Melilla', 'Murcia'],
name = 'Nacidos Hombres')

nacm = pd.Series([7962, 2221, 730, 9395],


index=['Canarias', 'Cantabria', 'Melilla', 'Galicia'],
name = 'Nacidas Mujeres')

nach

Cantabria 2154
Canarias 8184
Galicia 10032
Melilla 780
Murcia 8177
Name: Nacidos Hombres, dtype: int64
nacm

Canarias 7962
Cantabria 2221
Melilla 730
Galicia 9395
Name: Nacidas Mujeres, dtype: int64

nach + nacm

Canarias 16146.0
Cantabria 4375.0
Galicia 19427.0
Melilla 1510.0
Murcia NaN
dtype: float64

El resultado de la operación anterior contiene el valor NaN. Este valor se usa para representar un campo
vacío o desconocido. En este caso, la serie nach tiene valor 8177 para la etiqueta Murcia, pero la serie
nacm no contiene dicha etiqueta en su índice, por lo que la suma de ambos produce como resultado NaN.

Aunque los valores NaN generan algunos problemas a la hora de realizar ciertos análisis, puede ser útil
de nir una serie a partir de un array que contenga uno o varios valores NaN.

nacm = pd.Series([7962, np.NaN, 730, np.NaN],


index=['Canarias', 'Cantabria', 'Melilla', 'Galicia'],
name = 'Nacidas Mujeres')

nacm

Canarias 7962.0
Cantabria NaN
Melilla 730.0
Galicia NaN
Name: Nacidas Mujeres, dtype: float64

Los métodos isnull y notnull de las Series permiten identi car aquellas etiquetas en el índice
asociadas a un valor NaN. Como resultado se obtiene una nueva serie cuyos valores son de tipo Bool.

nacm.isnull()

Canarias False
Cantabria True
Melilla False
Galicia True
Name: Nacidas Mujeres, dtype: bool

nacm.notnull()

Canarias True
Cantabria False
Melilla True
Galicia False
Name: Nacidas Mujeres, dtype: bool

El análisis descriptivo de los datos representados mediante una serie se puede realizar usando los
métodos mean (media), median (mediana), std(desviación estándar), min(mínimo), max(máximo) y
quantile(quantiles) o usando el método describe de las Series:
nach.mean(), nach.median(), nach.std()

(5865.4, 8177.0, 4114.478679006613)

nach.describe()

count 5.000000
mean 5865.400000
std 4114.478679
min 780.000000
25% 2154.000000
50% 8177.000000
75% 8184.000000
max 10032.000000
Name: Nacidos Hombres, dtype: float64

Como complemento a la información proporcionada por el método describe, la librería Pandas junto
con Matplotlib permiten la creación de los grá cos más comunes en estadística descriptiva. Para crear un
diagrama de barras (bar) y un diagrama de tarta (pie) escribimos lo siguiente:

figura, ax1 = plt.subplots(1, figsize = (9,3))


nach.plot(ax=ax1, kind = 'bar', title = 'Diagrama de barras - Hombres');

La variable figura representa el lienzo sobre el que pintaremos uno o varios grá cos. En el ejemplo
anterior, pintamos un único grá co llamado ax1.

figura, ax2 = plt.subplots(1, figsize = (7,3))


nach.plot(ax=ax2, kind = 'pie', title = 'Diagrama de tarta - Hombres');
Si queremos dibujar dos grá cos en el mismo lienzo, en la misma figura, indicaremos que nuestro
lienzo se compone de una la nrows = 1 y dos columnas cols= 2. Usaremos los parámetros nrows y
ncols e la funci´pn plt.subplots.

figura, (ax1, ax2) = plt.subplots(nrows = 1, ncols= 2, figsize = (10,3))


nach.plot(ax=ax1, kind = 'bar', title = 'Diagrama de barras - Hombres');
nach.plot(ax=ax2, kind = 'pie', title = 'Diagrama de tarta - Hombres');

El curso CFI Visualización de Datos con Python está dedicado por


completo a la visualización de datos en Python utilizando dos librerías
diferentes: Matplotlib (para crear imágenes grá cas) y Plotly (para
crear visualizaciones interactivas). Muy útil para aquellos que
necesiten crear informes, grá cos y visualizaciones que ayuden a
explicar la información extraída de los datos. Aquí tienes una lección
de ejemplo link.

La gestión de valores duplicados se realiza mediante los métodos unique y value_count de las Series.
El método unique devuelve un array con los valores de la serie sin duplicados. A continuación creamos
una serie con valores duplicados:

pos = pd.Series(['Sur', 'Norte', 'Sur', 'Norte', 'Norte'],


index=['Cádiz', 'Cantabria', 'Melilla', 'Galicia', 'Asturias'])

pos

Cádiz Sur
Cantabria Norte
Melilla Sur
Galicia Norte
Asturias Norte
dtype: object

pos.unique()

array(['Sur', 'Norte'], dtype=object)


Por otro lado, el método value_counts devuelve la frecuencia de cada uno de los valores de una serie:

pos.value_counts()

Norte 3
Sur 2
dtype: int64

Los datos de tipo DataFrame

Los DataFrame de Pandas están diseñados para manejar datos representados en forma de tabla, donde
tanto las las como las columnas están indexadas. Los datos de tipo DataFrame pueden verse como una
colección ordenada de columnas, cada una de las cuales viene representada por un objeto de tipo
Series, con su nombre y su índice. Cada columna tiene asociado un tipo de datos y todas las columnas
comparten el mísmo índice. En este caso, usaremos el término dataframe para referirnos a los datos de
tipo DataFrame.

Hay varias formas de crear datos de tipo DataFrame.

Podemos usar la función pd.DataFrame si los datos se encuentran representados mediante una
lista, un diccionario (de listas o de arrays), una Serie o incluso otro DataFrame.
Si los datos en encuentran en un chero, Pandas proporciona las funciones pd.read_csv,
pd.read_excel para la creación de DataFrame.
En el siguiente ejemplo usamos la función pd.DataFrame para crear un dataframe a partir de un
diccionario:

d = { 'Ciudades' : ['Melilla', 'Canarias', 'Galicia', 'Cantabria', 'Murcia'],


'Nacidos H': [780, 8184, 10032, 2154, 8177] }
tabla = pd.DataFrame( data = d)
tabla

Ciudades Nacidos H

0 Melilla 780

1 Canarias 8184

2 Galicia 10032

3 Cantabria 2154

4 Murcia 8177

Las claves del diccionario forman el índice de las columnas. El valor de cada una de las claves del
diccinario ha de ser una lista o un array de igual longuitud. Al igual que las Series, los atributos index y
values representan el índice y los datos del dataframe respectivamente:
tabla.index

RangeIndex(start=0, stop=5, step=1)

tabla.values

array([['Melilla', 780],
['Canarias', 8184],
['Galicia', 10032],
['Cantabria', 2154],
['Murcia', 8177]], dtype=object)

A diferencia de las series, que tienen un único índice, los dataframes tienen dos índices. El primero es un
índice común para todas las columnas y está asociado a cada una de las las. El segundo índice contiene
un array de etiquetas o nombres de columnas. La propiedad columns representa el índice de las
columnas:

tabla.columns

Index(['Ciudades', 'Nacidos H'], dtype='object')

Para crear un DataFrame a partir de una serie, escribimos lo siguiente:

nat = pd.Series([10.7, 7.5, 7.1, 17.8, 7.9 ],


index=['Murcia','Cantabria', 'Galicia', 'Melilla','Canarias' ],
name = 'Valor')

nat

Murcia 10.7
Cantabria 7.5
Galicia 7.1
Melilla 17.8
Canarias 7.9
Name: Valor, dtype: float64

tabla2 = pd.DataFrame(nat)

tabla2

Valor

Murcia 10.7

Cantabria 7.5

Galicia 7.1

Melilla 17.8

Canarias 7.9

El índice del DataFrame creado coincide con el índice de la serie nat y el nombre de la columna es el
nombre de la Serie.
Una forma de añadir más información al dataframe es dar nombre a cada uno de los índices.

tabla2.index.name = 'Comunidades'
tabla2.columns.name = 'Tasa de Natalidad'

tabla2

Tasa de Natalidad Valor

Comunidades

Murcia 10.7

Cantabria 7.5

Galicia 7.1

Melilla 17.8

Canarias 7.9

La función pd.DataFrame admite más argumentos, como por ejemplo los argumentos index y columns
para crear el índice de las las y columnas del DataFrame respectivamente. En el siguiente ejemplo
creamos un DataFrame con datos acerca de la producción de mantequilla en miles de toneladas de
ciertos paises en distintos años (fuente: http://ec.europa.eu/eurostat/web/agriculture/data/main-tables):

tabla3 = pd.DataFrame( data = [ ('Estonia', 8.4, 7.7, 6.7, 'A'),


('Ireland',193.5, 227.76, 221.05, 'B'),
('Greece',1.4,1.6,1.5, 'A'),
('Spain',50.52,58.9,46.72, 'C'),
('France',415.09,423.1,400.52, 'A') ],
columns = ['Desc', '2004', '2005', '2006', 'Tipo'],
index = [ 'EE', 'IE', 'EL', 'ES', 'FR'])
tabla3

Desc 2004 2005 2006 Tipo

EE Estonia 8.40 7.70 6.70 A

IE Ireland 193.50 227.76 221.05 B

EL Greece 1.40 1.60 1.50 A

ES Spain 50.52 58.90 46.72 C

FR France 415.09 423.10 400.52 A

El método info de los datos de tipo DataFrame muestra información acerca del objeto DataFrame (el
número de columnas, el número de las, la cantidad de memoria usada), e información relativa a cada
una de las columnas (el nombre de las columnas, su tipo y el número de valores distinto de NaN).
tabla3.info()

<class 'pandas.core.frame.DataFrame'>
Index: 5 entries, EE to FR
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Desc 5 non-null object
1 2004 5 non-null float64
2 2005 5 non-null float64
3 2006 5 non-null float64
4 Tipo 5 non-null object
dtypes: float64(3), object(2)
memory usage: 240.0+ bytes

El método describe muestra los primeros estadísticos, lo que permite hacer un análisis de los datos en
cada una de las columnas numéricas.

tabla3.describe()

2004 2005 2006

count 5.000000 5.000000 5.000000

mean 133.782000 143.812000 135.298000

std 175.270242 180.976401 173.191749

min 1.400000 1.600000 1.500000

25% 8.400000 7.700000 6.700000

50% 50.520000 58.900000 46.720000

75% 193.500000 227.760000 221.050000

max 415.090000 423.100000 400.520000

Los datos mostrados son la media (mean), la mediana (median), la desviación estándar(std), el minimo
(min), el máximo (max) y los quantiles (quantile).
figura, ax1 = plt.subplots(1, figsize = (9,3))

tabla3.plot(ax=ax1, kind = 'bar', title = 'Producción de mantequilla')


ax1.set_xlabel('Países de la UE')
ax1.set_ylabel('Producción (x 1000 Toneladas)')
ax1.set_xticklabels(tabla3.Desc);

Al igual que las Series, los DataFrame también disponen de métodos estadísticos (mean, std, min, etc).
Estos métodos devuelven un valor solo para aquellas columnas numéricas del objeto DataFrame.

tabla3.mean()

2004 133.782
2005 143.812
2006 135.298
dtype: float64

tabla3.std()

2004 175.270242
2005 180.976401
2006 173.191749
dtype: float64

Todas las operaciones estadísticas admiten el argumento axis, el cual permite operar bien sobre los
datos de las columnas o bien sobre los datos en las las.

tabla3.sum(axis = 1)

EE 22.80
IE 642.31
EL 4.50
ES 156.14
FR 1238.71
dtype: float64

Una operación muy interesante cuando tratamos con datos representados mediante dataframes, es la de
trasponer, es decir, cambiar las por columnas. Para trasponer un dataframe es necesario utilizar el
atributo T.
tabla3.T

EE IE EL ES FR

Desc Estonia Ireland Greece Spain France

2004 8.4 193.5 1.4 50.52 415.09

2005 7.7 227.76 1.6 58.9 423.1

2006 6.7 221.05 1.5 46.72 400.52

Tipo A B A C A

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python Data Analysis Library
Python for Data Analysis
Table of Contents

1 Acceso y actualización de los datos


1.1 Acceso a los datos
1.1.1 Selección por etiqueta - loc
1.1.2 Selección por posición - iloc
1.1.3 Selección mediante ltros
1.2 Actualización de datos de un DataFrame
1.2.1 Modi cando valores del DataFrame
1.2.2 Eliminar columnas en un DataFrame
1.2.3 Insertar datos en un DataFrame
1.3 Referencias

Sección 4.2

Acceso y actualización de los datos

En esta Sección mostramos los mecanismos para acceder a los datos contenidos en una serie o un
dataframe, así como los mecanismos para realizar su actualización.

A continuación importamos las librerías necesarias para trabajar los contenidos.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

Acceso a los datos

La variable tabla3 represeta un DataFrame con datos acerca de la producción de mantequilla en miles
de toneladas de ciertos paises en distintos años.
tabla3 = pd.DataFrame( data = [ ('Estonia', 8.4, 7.7, 6.7, 'A'),
('Ireland',193.5, 227.76, 221.05, 'B'),
('Greece',1.4,1.6,1.5, 'A'),
('Spain',50.52,58.9,46.72, 'C'),
('France',415.09,423.1,400.52, 'A') ],
columns = ['Desc', '2004', '2005', '2006', 'Tipo'],
index = [ 'EE', 'IE', 'EL', 'ES', 'FR'])
tabla3

Desc 2004 2005 2006 Tipo

EE Estonia 8.40 7.70 6.70 A

IE Ireland 193.50 227.76 221.05 B

EL Greece 1.40 1.60 1.50 A

ES Spain 50.52 58.90 46.72 C

FR France 415.09 423.10 400.52 A

Para acceder a las columnas, podemos usar la notación '.' o con la notación empleada en los diccionarios,
es decir, notación de corchetes y el nombre de la columna. Como podemos observar, el resultado es un
objeto de tipo Series:

tabla3.Desc

EE Estonia
IE Ireland
EL Greece
ES Spain
FR France
Name: Desc, dtype: object

tabla3['2004']

EE 8.40
IE 193.50
EL 1.40
ES 50.52
FR 415.09
Name: 2004, dtype: float64

Para acceder a partes de un DataFrame, conjunto de las, conjunto de columnas, etc., se puede utilizar la
misma notación empleada para los arrays, pero esa opción es poco e ciente en el caso de DataFrame.
Una alternativa mas e ciente es utilizar los indexadores loc e iloc. Veamos cada uno de ellos:

Selección por etiqueta - loc

Permite recuperar partes del DataFrame indicando el nombre las de las y columnas que queremos
seleccionar, es decir, indicando la/las etiquetas de cada uno de los índices. Presentamos algunos
ejemplos que muestran el comportamiento. Para recuperar los datos de los países desde 'IE' hasta 'ES'
escribimos los siguiente:

tabla3.loc['IE': 'ES']

Desc 2004 2005 2006 Tipo

IE Ireland 193.50 227.76 221.05 B

EL Greece 1.40 1.60 1.50 A

ES Spain 50.52 58.90 46.72 C

Si solo nos interesan la producción en los años 2004 y 2005 de los paises desde 'IE' hasta 'ES' escribimos
los siguiente:

res = tabla3.loc['IE':'ES', ['2004', '2005']]


res

2004 2005

IE 193.50 227.76

EL 1.40 1.60

ES 50.52 58.90

Para seleccionar los datos de producción de solo dos paises ('IE' y 'ES') en los años 2004 y 2006,
escribimos lo siguiente:

res = tabla3.loc[['IE','ES'], ['2004', '2006']]


res

2004 2006

IE 193.50 221.05

ES 50.52 46.72

Para seleccionar un valor escalar, por ejemplo la producción de mantequilla en el año 2006 en el pais 'IE',
escribimos:

tabla3.loc['IE', '2006']

221.05

Selección por posición - iloc

El indexador iloc permite recuperar partes del DataFrame indexando las y columnas con valores de
tipo int comenzando desde el 0. Para seleccionar la primera la del DataFrame escribimos lo siguiente:
tabla3.iloc[0]

Desc Estonia
2004 8.4
2005 7.7
2006 6.7
Tipo A
Name: EE, dtype: object

El resultado es un objeto de tipo Series. El nombre de la serie coincide con el valor del índice de la la
seleccionada. Para recuperar las tres primeras las y la primera y última columna, escribimos lo siguiente:

tabla3.iloc[:3,[0,-1]]

Desc Tipo

EE Estonia A

IE Ireland B

EL Greece A

Para seleccionar un valor escalar, podemos utilizar iloc de la siguiente forma:

tabla3.iloc[1,0]

'Ireland'

Selección mediante iltros

Otra forma de seleccionar partes de un dataframe es mediante el uso de ltros, de forma similar a cómo
se aplicaban en el caso de los arrays de Numpy.

tabla2 = pd.DataFrame([10.7, 7.5, 7.1, 17.8, 7.9 ],


columns = ['Valor'],
index = ['Murcia','Cantabria', 'Galicia', 'Melilla','Canarias' ])

tabla2

Valor

Murcia 10.7

Cantabria 7.5

Galicia 7.1

Melilla 17.8

Canarias 7.9
tabla2[tabla2 < 10]

Valor

Murcia NaN

Cantabria 7.5

Galicia 7.1

Melilla NaN

Canarias 7.9

Al aplicar el ltro, se mantienen los valores que cumplen el ltro y el resto se sustituyen por NaN.

Actualización de datos de un DataFrame

En muchas ocasiones los datos que se están analizando no continen toda la información que
necesitamos para nuestro análisis, o símplemente no están en el formato apropiado. En estos casos,
Pandas ofrece la posibilidad de modi car los valores del DataFrame, borrar columnas, crear columnas
con nuevos valores o incluso columnas cuyos valores se calculan a partir de los valores de otras
columnas.

Modi icando valores del DataFrame

Para actualizar los datos de una columna podemos utilizar un valor concreto o un array de valores.

tabla3.Tipo = 'D'

tabla3

Desc 2004 2005 2006 Tipo

EE Estonia 8.40 7.70 6.70 D

IE Ireland 193.50 227.76 221.05 D

EL Greece 1.40 1.60 1.50 D

ES Spain 50.52 58.90 46.72 D

FR France 415.09 423.10 400.52 D

tabla3.Tipo = ['D', 'D', 'D', 'E', 'F']


tabla3

Desc 2004 2005 2006 Tipo

EE Estonia 8.40 7.70 6.70 D

IE Ireland 193.50 227.76 221.05 D

EL Greece 1.40 1.60 1.50 D

ES Spain 50.52 58.90 46.72 E

FR France 415.09 423.10 400.52 F

También podemos modi car el valor de una columna aplicando una función de transformación. Por
ejemplo, si queremos transformar los valores de la columna Desc a mayúsculas, escribimos lo siguiente:

tabla3.Desc = tabla3.Desc.map(lambda x :str.upper(x))

tabla3

Desc 2004 2005 2006 Tipo

EE ESTONIA 8.40 7.70 6.70 D

IE IRELAND 193.50 227.76 221.05 D

EL GREECE 1.40 1.60 1.50 D

ES SPAIN 50.52 58.90 46.72 E

FR FRANCE 415.09 423.10 400.52 F

La función map aplica la función lambda a cada uno de los valores de la columna. Si queremos modi car
todos los datos de tipo numérico del DataFrame de forma que los datos se expresen con un único
decimal, usamos la función applymap junto con una función lambda como se muestra a continuación:
tabla3 = tabla3.applymap(lambda x : round(x,1) if type(x) == float else x)
tabla3

Desc 2004 2005 2006 Tipo

EE ESTONIA 8.4 7.7 6.7 D

IE IRELAND 193.5 227.8 221.1 D

EL GREECE 1.4 1.6 1.5 D

ES SPAIN 50.5 58.9 46.7 E

FR FRANCE 415.1 423.1 400.5 F

Aquí se le ve el sentido que tiene estudiar las funciones como argumementos de otras funciones, así
como las funciones lambda. Si quieres refrescar estos temas, repasa la Sección 2.4.

Eliminar columnas en un DataFrame

La operación pop de los DataFrame elimina la columna especi cada y devuelve dicha columna en un
objeto de tipo Series. Esta operación modi ca el DataFrame. Por otro lado, la operación drop permite
borrar tanto las como columnas, con la diferencia de que no devuelve los elementos borrados y no
modi ca el Dataframe, sino que devuelve una copia:

columna_tipo = tabla3.pop('Tipo')
columna_tipo

EE D
IE D
EL D
ES E
FR F
Name: Tipo, dtype: object

type(columna_tipo)

pandas.core.series.Series

tabla3

Desc 2004 2005 2006

EE ESTONIA 8.4 7.7 6.7

IE IRELAND 193.5 227.8 221.1

EL GREECE 1.4 1.6 1.5

ES SPAIN 50.5 58.9 46.7

FR FRANCE 415.1 423.1 400.5


También es posible utilizar la operación drop para eliminar tanto las como columnas.

nuevo_df = tabla3.drop(['IE','FR'])
nuevo_df

Desc 2004 2005 2006

EE ESTONIA 8.4 7.7 6.7

EL GREECE 1.4 1.6 1.5

ES SPAIN 50.5 58.9 46.7

tabla3

Desc 2004 2005 2006

EE ESTONIA 8.4 7.7 6.7

IE IRELAND 193.5 227.8 221.1

EL GREECE 1.4 1.6 1.5

ES SPAIN 50.5 58.9 46.7

FR FRANCE 415.1 423.1 400.5

Para borrar columnas con drop, es necesario indicarlo mediante el argumento axis:

tabla3.columns

Index(['Desc', '2004', '2005', '2006'], dtype='object')

tabla3.drop(['Desc', '2006'], axis = 1)

2004 2005

EE 8.4 7.7

IE 193.5 227.8

EL 1.4 1.6

ES 50.5 58.9

FR 415.1 423.1
tabla3

Desc 2004 2005 2006

EE ESTONIA 8.4 7.7 6.7

IE IRELAND 193.5 227.8 221.1

EL GREECE 1.4 1.6 1.5

ES SPAIN 50.5 58.9 46.7

FR FRANCE 415.1 423.1 400.5

Insertar datos en un DataFrame

Para añadir nuevas columnas al DataFrame, ya sean calculadas o no, podemos usar la notación de
corchetes junto con el nombre de la nueva columna, al estilo de los diccionarios:

tabla3['Unidad'] = '1000 T'


tabla3

Desc 2004 2005 2006 Unidad

EE ESTONIA 8.4 7.7 6.7 1000 T

IE IRELAND 193.5 227.8 221.1 1000 T

EL GREECE 1.4 1.6 1.5 1000 T

ES SPAIN 50.5 58.9 46.7 1000 T

FR FRANCE 415.1 423.1 400.5 1000 T

tabla3['2009'] = [7.71, 188.1, 1.1, 36.7, 410.09 ]


tabla3

Desc 2004 2005 2006 Unidad 2009

EE ESTONIA 8.4 7.7 6.7 1000 T 7.71

IE IRELAND 193.5 227.8 221.1 1000 T 188.10

EL GREECE 1.4 1.6 1.5 1000 T 1.10

ES SPAIN 50.5 58.9 46.7 1000 T 36.70

FR FRANCE 415.1 423.1 400.5 1000 T 410.09


O podemos crear una columna nueva calculada a partir de otras columnas del DataFrame. Por ejemplo,
para crear la columna de totales, escribimos lo siguiente:

tabla3['Total'] = tabla3['2004'] + tabla3['2005'] + tabla3['2006']


tabla3

Desc 2004 2005 2006 Unidad 2009 Total

EE ESTONIA 8.4 7.7 6.7 1000 T 7.71 22.8

IE IRELAND 193.5 227.8 221.1 1000 T 188.10 642.4

EL GREECE 1.4 1.6 1.5 1000 T 1.10 4.5

ES SPAIN 50.5 58.9 46.7 1000 T 36.70 156.1

FR FRANCE 415.1 423.1 400.5 1000 T 410.09 1238.7

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python Data Analysis Library
Python for Data Analysis
Sección 4.3

Operaciones con Series y Dataframes

Una de las características mas importante en torno a los índices de las estructuras de datos en Pandas es
el alineamiento. Pandas es capaz de alinear dos estructuras (series o dataframes) en base a las etiquetas
de sus índices. Esta propiedad es especialmente interesante cuando se realizan operaciones aritméticas,
teniendo en cuenta que dichas operaciones pueden involucar estructuras con etiquetas comunes en sus
índices, índices no ordenados, incluso puede darse el caso de tener etiquetas en solo uno de los índices.

En esta Sección mostramos algunos ejemplos de operaciones. En primer lugar cremos dos dataframes
con distintas etiquetas para sus índices.

Operaciones aritméticas y alineamiento de datos

import numpy as np
import pandas as pd

datos_1 = np.array([[ 1000, 3000],


[ 5000, 1000],
[ 4200, 6000],
[ 5000, 5500]])
t1 = pd.DataFrame(datos_1, columns= ('Gastos', 'Ingresos'),
index = [2006, 2002, 2005, 2007])

t1

Gastos Ingresos

2006 1000 3000

2002 5000 1000

2005 4200 6000

2007 5000 5500


datos_2 = np.array([[ 1000, 5000],
[ 5000, 5000],
[ 2500, 3000],
[ 2500, 1500]])
t2 = pd.DataFrame(datos_2, columns= ( 'Gastos', 'Ingresos'),
index = [2002,2006, 2007, 2008])
t2

Gastos Ingresos

2002 1000 5000

2006 5000 5000

2007 2500 3000

2008 2500 1500

Como podemos observar, ambos dataframes tienen algunas etiquetas comunes en sus índices y otras
que solo aparecen en uno de los dataframes. En el siguiente ejemplo, mostramos cómo se comporta la
operación suma aplicando el operador (+).

t1 + t2

Gastos Ingresos

2002 6000.0 6000.0

2005 NaN NaN

2006 6000.0 8000.0

2007 7500.0 8500.0

2008 NaN NaN

Cuando una etiqueta aparece en el índice de ambos dataframes, se realiza la suma de los valores. En
caso contrario se asigna el valor NaN. Este es el comportamiento para todos los operadores aritméticos.
Sin embargo, como alternativa a ésta perdida de información, es posible utilizar los métodos add, sub,
div y mul, que implementan la operación suma, resta, división y multiplicación respectivamente. El
argumento fill_value permite indicar el valor por defecto para aquellas etiquetas que no aparecen en
el índice.
t1.add(t2, fill_value = 0 )

Gastos Ingresos

2002 6000.0 6000.0

2005 4200.0 6000.0

2006 6000.0 8000.0

2007 7500.0 8500.0

2008 2500.0 1500.0

El comportamiento es análogo en el caso de operar con diferentes estructuras de datos, por ejemplo, con
un dataframe y una serie.

t1

Gastos Ingresos

2006 1000 3000

2002 5000 1000

2005 4200 6000

2007 5000 5500

s1 = pd.Series([100, 200], index = ['Gastos', 'Ingresos'])

s1

Gastos 100
Ingresos 200
dtype: int64
t1 - s1

Gastos Ingresos

2006 900 2800

2002 4900 800

2005 4100 5800

2007 4900 5300

Como se puede ver, el índice de la serie coincide con el índice de las columnas del dataframe. En ese
caso, los valores de la serie se restan a los valores del dataframe para cada columna donde existe
coincidencia. Si existe una etiqueta que no existe en alguna de las dos extructuras, el resultado de la
operación tendrá una nueva columna con dicha etiqueta cuyos valores serán NaN.

s2 = pd.Series([100, 200, 100], index = ['Gastos', 'Ingresos', 'Nuevo'])

s2

Gastos 100
Ingresos 200
Nuevo 100
dtype: int64

t1 - s2

Gastos Ingresos Nuevo

2006 900 2800 NaN

2002 4900 800 NaN

2005 4100 5800 NaN

2007 4900 5300 NaN

Ordenación de índices y valores

La ordenación de los datos por algún criterio es una operación muy habitual cuando se trata de analizar
datos. El método sort_index de los DataFrame ordena lexicográ camente los índices de un dataframes
en base a cualquiera de sus índices.
t1

Gastos Ingresos

2006 1000 3000

2002 5000 1000

2005 4200 6000

2007 5000 5500

t1.sort_index()

Gastos Ingresos

2002 5000 1000

2005 4200 6000

2006 1000 3000

2007 5000 5500

Es posible indicar mediante el argumento ascending el tipo de ordenación ascendente o descendente.

t1.sort_index(ascending = False)

Gastos Ingresos

2007 5000 5500

2006 1000 3000

2005 4200 6000

2002 5000 1000

Para ordenar el índice asociado a las columnas utilizamos el argumento axis con valor 1.
t1.sort_index(axis = 1, ascending = False)

Ingresos Gastos

2006 3000 1000

2002 1000 5000

2005 6000 4200

2007 5500 5000

Para ordenar los valores de una serie o un dataframe usamos el método sort_values. En el caso de los
dataframes, es necesario indicar las columnas (mediante una lista) por las que queremos ordenar en el
argumento by.

t1.sort_values(by = ['Gastos'])

Gastos Ingresos

2006 1000 3000

2005 4200 6000

2002 5000 1000

2007 5000 5500

Por defecto, la ordenación es ascendente en todas las columnas, pudiendo indicar otro orden mediante
una lista de valores de tipo bool en el argumento ascending.

t1.sort_values(by = ['Gastos', 'Ingresos'] , ascending = [True, False])

Gastos Ingresos

2006 1000 3000

2005 4200 6000

2007 5000 5500

2002 5000 1000

Como resultado obtenemos un nuevo dataframe ordenado de forma ascendente en cuanto a la columna
Gastos y descendente en cuanto a la columna Ingresos.
Tratamiento de valores NaN

Resulta bastante habitual cuando se trabaja con datos, encontrarse con valores vacíos o desconocidos. Ya
hemos comentado anteriormente que estos valores se identi can con NaN(Not a Number). Aunque
Pandas es capaz de tratar este tipo de valores de forma implícita, resulta de mucha utilidad conocer las
herramientas que proporciona Pandas para su manejo.

Por un lado, es posible asignar valores NaN de forma explícita usando el valor np.nan de la librería
NumPy.

s3 = pd.Series([1, np.nan, 2], index = ['A', 'B', 'C'])

s3

A 1.0
B NaN
C 2.0
dtype: float64

Una de las opciones para eliminar masivamente estos valores, es usar el método dropna. El resultado es
una nueva estructura de datos sin valores NaN.

s3.dropna()

A 1.0
C 2.0
dtype: float64

s3

A 1.0
B NaN
C 2.0
dtype: float64

En el caso de dataframes, los valores NaN pueden estar distribuídos en varias columnas.

t3 = pd.DataFrame( data = [ (np.nan, 'ES', 46449, np.nan),


('Alemania', np.nan , np.nan, np.nan),
('Japón', 'JP', np.nan, '2005') ],
columns = ['País', 'Código', 'Pob.', 'Año'])
t3

País Código Pob. Año

0 NaN ES 46449.0 NaN

1 Alemania NaN NaN NaN

2 Japón JP NaN 2005

La operación dropna eliminará todas las las y todas las columnas que tengan algún valor NaN.
t3.dropna()

País Código Pob. Año

Para eliminar aquellas las en las que los valores de todas las columnas son NaN, usamos las opciones
axis y how con valores 0 y all respectivamente.

t3.dropna(how = 'all', axis = 0 )

País Código Pob. Año

0 NaN ES 46449.0 NaN

1 Alemania NaN NaN NaN

2 Japón JP NaN 2005

Y si queremos eliminar aquellas las o columnas que no alcanzan un número determinado de valores
distinto de NaN, usamos la opción thresh. Por ejemplo, para eliminar aquellas las que no tienen al
menos 3 valores distintos de Nan, escribiremos:

t3.dropna( axis = 0, thresh = 3 )

País Código Pob. Año

2 Japón JP NaN 2005

En los casos en los no deseamos eliminar datos con valores NaN, Pandas ofrece la posibilidad de sustituir
dichos valores por un valor por defecto. Para ello usamos el método fillna junto con el valor a sustituir.

t3.fillna(0)

País Código Pob. Año

0 0 ES 46449.0 0

1 Alemania 0 0.0 0

2 Japón JP 0.0 2005

También es posible indicar un valor por defecto distinto para cada columna. En ese caso, usaremos un
diccionario como argumento del método fillna. Las claves del diccionario serán las etiquetas del índice
de las columnas afectadas y el valor asociado a cada una de las claves, será el valor por defecto que
sustituye a NaN.
d = {'País': 'ZZ', 'Código': 'ZZ', 'Pob.': 0}
t3.fillna(d)

País Código Pob. Año

0 ZZ ES 46449.0 NaN

1 Alemania ZZ 0.0 NaN

2 Japón JP 0.0 2005

En las operaciones estadísticas, Pandas ignora los valores NaN, pero si no queremos ignorarlos ha de
usarse el argumento skipna.

El siguiente dataframe contiene cierta información de tres países (código de país en el índice), en tres
ejercicios (columnas 2004, 2005 y 2006).

t4 = pd.DataFrame( data = [ ( 8, 7, 6),


(19, np.nan, 221),
(1,1,np.nan) ],
columns = [ '2004', '2005', '2006'],
index = [ 'EE', 'IE', 'EL'])
t4

2004 2005 2006

EE 8 7.0 6.0

IE 19 NaN 221.0

EL 1 1.0 NaN

También es posible calcular la media por columnas. Por defecto se ignoran los valores NaN, pero si no
queremos ignorarlos ha de usarse la opción skipna con valor False.

t4.mean(skipna=False)

2004 9.333333
2005 NaN
2006 NaN
dtype: float64

Relación entre variables: Correlación y covarianza

Entre las operaciones estadísticas más comunes se encuentran la correlación y la covarianza. Estas
operaciones se representan en Pandas mediante los métodos corr y cov respectivamente. Ambos
métodos se encuentran de nidos tanto para las Series como para los DataFrame. Así, es posible
calcular la correlación entre cualquier par de columnas de un dataframe, obteniéndose lo que se
denomina una matriz de correlación.

datos = [[40, 58, 115],


[ 43, 59, 117],
[ 41, 60, 120],
[ 38, 61, 120],
[ 36, 62, 126],
[ 40, 63, 122]]

t5 = pd.DataFrame(datos, columns = ['Edad', 'Peso', 'Altura'])


t5

Edad Peso Altura

0 40 58 115

1 43 59 117

2 41 60 120

3 38 61 120

4 36 62 126

5 40 63 122

mcorr = t5.corr()
mcorr

Edad Peso Altura

Edad 1.000000 -0.529641 -0.708300

Peso -0.529641 1.000000 0.861444

Altura -0.708300 0.861444 1.000000

La matriz de correlación se puede representar mediante un mapa de calor como se muestra a


continuación. Utilizamos la función heatmap de la librería Seaborn.
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

f, ax = plt.subplots(1, figsize=(4, 3))


sns.heatmap(mcorr, vmax=1, square=False, annot=True, fmt='.2f')
ax.set_title('Matriz de correlación - Mapa de calor');

De forma análoga, el método cov devuelve como resultado un nuevo dataframe que representa la matriz
de covarianza.

t5.cov()

Edad Peso Altura

Edad 5.866667 -2.4 -6.6

Peso -2.400000 3.5 6.2

Altura -6.600000 6.2 14.8

t5.plot(kind='scatter', x='Altura', y='Peso');


plt.xlabel('Altura')
plt.ylabel('Peso');

De la misma forma, es posible calcular la correlación entre un par de columnas (correlación entre dos
series).

t5['Edad'].corr(t5['Altura'])

-0.7083002112022763

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python Data Analysis Library
Python for Data Analysis
Sección 5.1

Importar y exportar icheros

Aunque otras librerías de Python proporcionan herramientas para importar y exportar datos, Pandas
aporta una gran potencia a la hora de leer y/o escribir cheros de datos. En esta sección abordamos el
tema de cómo leer y almacenar datos en diferentes formatos, ya sea en cheros o en bases de datos.
Pandas proporciona una amplia colección de funciones para realizar la lectura de datos como un objeto
de tipo DataFrame de una forma sencilla y exible.

Comenzamos la Sección con el tratamiento de cheros de texto y cheros en formato Excel.


Posteriormente abordamos el caso de la lectura de datos que se encuentrar almacenados en Bases de
datos tanto SQL como NoSQL.

import pandas as pd
import numpy as np

Ficheros de texto o CSV

En muchas ocasiones los datos en encuentran almacenados en cheros de texto y representados en


forma de tabla. El formato más popular es el CSV , donde los valores de cada una de las las están
separados por el símbolo coma (,). En otras ocasiones, los valores de cada la están separados por otro
símbolo, un tabulador o un espacio. Este tipo de cheros normalmente tienen extensión .txt.

La función más utilizada en pandas para leer de forma exible cheros de texto plano es pd.read_csv.
La escritura de los datos contenidos en un dataframe o una serie se realiza mediante la operación
to_csv de las Series y DataFrame.

Lectura de icheros de texto o CSV

El chero animals1.csv contiene información del peso medio del cuerpo y cerebro de 8 especies de
animales:
animals1.csv
------------
Especie,Peso cuerpo,Peso cerebro
Big brown bat,0.023,0.3
Mouse,0.023,0.4
Ground squirrel,0.101,4
Tree shrew,0.104,2.5
Golden hamster,0.12,1
Mole,0.122,3
Galago,0.2,5
Rat,0.28,1.9

La función pd.read_csv permite crear un DataFrame a partir de los datos contenidos en un chero en
formato CSV.

dfcsv = pd.read_csv('datos/animals1.csv')
dfcsv

Especie Peso cuerpo Peso cerebro

0 Big brown bat 0.023 0.3

1 Mouse 0.023 0.4

2 Ground squirrel 0.101 4.0

3 Tree shrew 0.104 2.5

4 Golden hamster 0.120 1.0

5 Mole 0.122 3.0

6 Galago 0.200 5.0

7 Rat 0.280 1.9

Como podemos ver, la creación de dataframes a partir de un chero CSV es bastante sencilla.
Cuando los datos almacenados en el chero no contienen la información del nombre de cada una de las
columnas, es decir, los datos aparecen en la primera la del chero (ver el chero animals2.csv),
podemos indicar mediante el argumento header la ausencia de cabeceras.
animals2.csv
------------
Big brown bat,0.023,0.3
Mouse,0.023,0.4
Ground squirrel,0.101,4
Tree shrew,0.104,2.5
Golden hamster,0.12,1
Mole,0.122,3
Galago,0.2,5
Rat,0.28,1.9

Las columnas del dataframe se indexan de forma automática con valores de tipo int comenzando por el
valor 0.

dfcsv = pd.read_csv('datos/animals2.csv', header = None)


dfcsv

0 1 2

0 Big brown bat 0.023 0.3

1 Mouse 0.023 0.4

2 Ground squirrel 0.101 4.0

3 Tree shrew 0.104 2.5

4 Golden hamster 0.120 1.0

5 Mole 0.122 3.0

6 Galago 0.200 5.0

7 Rat 0.280 1.9

Usando el argumento names de la función pd.read_csv es posible asignar un nombre más signi cativo
a cada una de las columnas:
dfcsv = pd.read_csv('datos/animals2.csv',
header = None,
names = ['Especie', 'Peso Cuerpo', 'Peso cerebro'])
dfcsv

Especie Peso Cuerpo Peso cerebro

0 Big brown bat 0.023 0.3

1 Mouse 0.023 0.4

2 Ground squirrel 0.101 4.0

3 Tree shrew 0.104 2.5

4 Golden hamster 0.120 1.0

5 Mole 0.122 3.0

6 Galago 0.200 5.0

7 Rat 0.280 1.9

En algunas ocasiones las primeras líneas del chero no se corresponden con datos propiamente dichos,
si no que símplemente aportan una descripción de los datos que contiene (ver peliculas.csv).

peliculas.csv
------------
Cine de los 90
*********************************
Estreno: Fecha de estreno
Nombre: Título de la película
Cat: Género de la película
*********************************
Estreno,Nombre,Cat
1982,E.T. the ExtraTerrestrial,Fantasy
1982,Poltergeist,Horror
1992,Alien,Action
1992,The Crying Game,War
1995,Toy Story,Animation
1995,GoldenEye,Action
1995,Four Rooms,Thriller

El argumento skiprows permite omitir la lectura de un número determinado de las al comienzo del
chero.

dfcsv = pd.read_csv('datos/peliculas.csv', skiprows = 6)


dfcsv

Estreno Nombre Cat

0 1982 E.T. the ExtraTerrestrial Fantasy

1 1982 Poltergeist Horror

2 1992 Alien Action

3 1992 The Crying Game War

4 1995 Toy Story Animation

5 1995 GoldenEye Action

6 1995 Four Rooms Thriller

También es posible omitir la lectura de ciertas las indicándolo mediante una lista de las en el
argumentos skiprows.

dfcsv = pd.read_csv('datos/peliculas.csv',
skiprows = [0, 1, 2, 3, 4 , 5, 6, 7, 8, 9])
dfcsv

1992 The Crying Game War

0 1995 Toy Story Animation

1 1995 GoldenEye Action

2 1995 Four Rooms Thriller

Para crear variables de tipo DataFrame donde los valores de una de las columnas en el chero se van a
convertir en el índice del dataframe, usamos el argumento index_col:
dfcsv = pd.read_csv('datos/peliculas.csv', skiprows = 6,
index_col = ['Estreno'])
dfcsv

Nombre Cat

Estreno

1982 E.T. the ExtraTerrestrial Fantasy

1982 Poltergeist Horror

1992 Alien Action

1992 The Crying Game War

1995 Toy Story Animation

1995 GoldenEye Action

1995 Four Rooms Thriller

También es posible crear dataframes donde el índice se compone de varias columnas del chero; se trata
de índices jerárquicos. Por ejemplo, podemos usar el argumento index_col con la lista de columnas del
chero que formará el índice del dataframe creado:

dfcsv = pd.read_csv('datos/peliculas.csv', skiprows = 6,


index_col = [0, 1])
dfcsv

Cat

Estreno Nombre

1982 E.T. the ExtraTerrestrial Fantasy

Poltergeist Horror

1992 Alien Action

The Crying Game War

1995 Toy Story Animation

GoldenEye Action

Four Rooms Thriller


Cuando los cheros de datos que queremos procesar son muy grandes, pandas ofrece la posibilidad de
realizar una lectura por partes. Esta opción permite procesar solo una parte de los datos. El argumento
nrows permite procesar un numero determinado de las del chero, mientras que el argumento
skiprows permite indicar las las que no queremos procesar.

dfcsv = pd.read_csv('datos/peliculas.csv', skiprows = 6, nrows =2)


dfcsv

Estreno Nombre Cat

0 1982 E.T. the ExtraTerrestrial Fantasy

1 1982 Poltergeist Horror

Escritura de datos en icheros CSV

Los datos almacenados en las estructuras de datos de Pandas, ya sea en Series o DataFrames, pueden
ser almacenados en cheros. Esta es una operación se realiza de forma habitual después del
procesamiento de datos, limpieza, etc. Por ejemplo, para escribir los datos incluídos en un DataFrame en
un chero CSV utilizamos el método to_csv con el nombre del chero como argumento.

dfcsv = pd.read_csv('datos/peliculas2.csv', index_col = ['Estreno'])


dfcsv

Nombre Minutos

Estreno

1982 E.T. the ExtraTerrestrial 114

1982 Poltergeist 91

1992 Alien 144

1992 The Crying Game 113

1995 Toy Story 81

1995 GoldenEye 130

1995 Four Rooms 94

Admite una gran cantidad de parámetros. Opcionalmente podemos generar un chero csv con cabeceras
o sin ellas, con índices o sin ellos.

dfcsv.to_csv('./datos/datos.csv')

datos.csv
------------
Estreno,Nombre,Minutos
1982,E.T. the ExtraTerrestrial,114
1982,Poltergeist,91
1992,Alien,144
1992,The Crying Game,113
1995,Toy Story,81
1995,GoldenEye,130
1995,Four Rooms,94

Como se puede ver en el ejemplo, tanto el índice como las columnas del DataFrame se escriben en el
chero. Los argumentos index y header permiten cambiar el comportamiento por defecto:

dfcsv.to_csv('./datos/datos.csv', header = True, index = False)

datos.csv
------------
Nombre,Minutos
E.T. the ExtraTerrestrial,114
Poltergeist,91
Alien,144
The Crying Game,113
Toy Story,81
GoldenEye,130
Four Rooms,94

En éste punto nos preguntamos qué sucede con los valores NaN existentes en un dataframe. Por defecto,
los valores NaN se muestran como campos vacíos en el chero.
col = [ 1, 2, np.nan, 4, np.nan, np.nan, np.nan]

dfcsv.insert(1, 'Nueva Columna', value = col)


dfcsv

Nombre Nueva Columna Minutos

Estreno

1982 E.T. the ExtraTerrestrial 1.0 114

1982 Poltergeist 2.0 91

1992 Alien NaN 144

1992 The Crying Game 4.0 113

1995 Toy Story NaN 81

1995 GoldenEye NaN 130

1995 Four Rooms NaN 94

dfcsv.to_csv('./datos/datos.csv', header = True, index = False)

datos.csv
------------
Nombre,Nueva Columna,Minutos
E.T. the ExtraTerrestrial,1.0,114
Poltergeist,2.0,91
Alien,,144
The Crying Game,4.0,113
Toy Story,,81
GoldenEye,,130
Four Rooms,,94

El argumento na_rep permite reemplazar los valores NaN del dataframe por otros que elija el usuario.

dfcsv.to_csv('datos.csv', header = True, index = False, na_rep = 0)

datos.csv
------------
Nombre,Nueva Columna,Minutos
E.T. the ExtraTerrestrial,1.0,114
Poltergeist,2.0,91
Alien,0,144
The Crying Game,4.0,113
Toy Story,0,81
GoldenEye,0,130
Four Rooms,0,94

Aunque los ejemplos anteriores solo tratan con dataframes, el comportamiento es análogo si tratamos
con series.

Ficheros en formato Microsoft Excel

Otra forma muy habitual para guardar datos en forma tabular es mediante hojas de cálculo en un libro o
chero de Microsoft Excel. Pandas incluye la función pd.read_excel para realizar la lectura de datos y
crear dataframes, mientras que el método to_excel de los Dataframe permite escribir los datos en una
hoja de cálculo de Microsoft Excel.

Lectura de icheros Excel

La función pd.read_excel permite crear objetos de tipo DataFrame a partir de los datos contenidos en
cheros con extensión .xls y .xlsx. La siguiente gura muestra el contenido de dos hojas contenidas
en el chero clientes.xlsx.

Por defecto, la función pd.read_excel lee los datos de la primera hoja dentro del libro, pero se puede
indicar que lea los datos de otra hoja usando el argumento sheet_name.
dfexcel = pd.read_excel('./datos/clientes.xlsx')
dfexcel

CODCLI NOMCLI SEXO

0 CLI-0001 ROSA ELVIRA F

1 CLI-0003 MIGUEL ANGEL M

2 CLI-0004 ANA MARIA F

3 CLI-0013 BRIGGITTE F

4 CLI-0018 SAULO ANDRE M

5 CLI-0020 MONICA F

dfexcel = pd.read_excel('./datos/clientes.xlsx', sheet_name = "Hoja2")


dfexcel

CODCLI EUROS MAX

0 CLI-0001 89574 100000

1 CLI-0019 68752 80000

2 CLI-0020 63014 70000

La función pd.read_excel es tan exible como lo pueda ser la función pd.read_csv para cheros de
texto. Es posible saltar cabeceras, leer un número determinado de las, leer solo algunas de las columnas
contenidas en la hoja de cálculo, etc. Su funcionamiento varía dependiendo de los valores de los
argumentos cuando se invoca la función.

En el siguiente ejemplo aplicamos una función de conversión (función funtitle previamente de nida) a
los valores contenidos en la columna NOMCLI de la primera hoja del chero.
def funtitle(cadena):
return cadena.title()

dfexcel = pd.read_excel('./datos/clientes.xlsx', converters = {'NOMCLI': funtitle})


dfexcel

CODCLI NOMCLI SEXO

0 CLI-0001 Rosa Elvira F

1 CLI-0003 Miguel Angel M

2 CLI-0004 Ana Maria F

3 CLI-0013 Briggitte F

4 CLI-0018 Saulo Andre M

5 CLI-0020 Monica F

O lo que es lo mismo, pero usando funciones anónimas:

dfexcel = pd.read_excel('./datos/clientes.xlsx',
converters = {'NOMCLI': lambda x: x.title() })
dfexcel

CODCLI NOMCLI SEXO

0 CLI-0001 Rosa Elvira F

1 CLI-0003 Miguel Angel M

2 CLI-0004 Ana Maria F

3 CLI-0013 Briggitte F

4 CLI-0018 Saulo Andre M

5 CLI-0020 Monica F

El argumento converters recibe como valor un diccionario, cuyas claves son las columnas sobre las que
se va a aplicar una función de conversión. El valor asociado a cada clave es la función a aplicar. Observa
que esta función se ha de nido como una función lambda.

En el chero de Microsoft Excel clientes1.xlsx que se muestra a continuación:


La columna TLF tiene valor - en aquellas celdas donde no se conoce el dato, mientras que la columna
SEXO tiene celdas con valor Desconocido. También podemos observar que la columna NOMCLI tiene una
celda vacía. En el siguiente ejemplo mostramos cómo manejar estas situaciones:

dfexcel = pd.read_excel('./datos/clientes1.xlsx',
na_values = {'SEXO': ['Desconocido'],
'TLF' : ['-'] } )
dfexcel

CODCLI NOMCLI SEXO TLF

0 CLI-0001 ROSA ELVIRA F 7578.0

1 CLI-0003 MIGUEL ANGEL NaN NaN

2 CLI-0004 ANA MARIA NaN NaN

3 CLI-0013 BRIGGITTE F NaN

4 CLI-0018 SAULO ANDRE M NaN

5 CLI-0020 NaN F 7600.0

El argumento na_values permite sustituir valores contenidos en la hoja de cálculo por NaN. Dicho
argumento recibe un diccionario, cuyas claves son las columnas sobre las que se va a aplicar la
sustitución, y el valor de cada clave es la lista de valores que han de ser sustituidos por NaN. Las celdas
vacías en la hoja de cálculo se transforman automáticamente en NaN en el dataframe.

La clase ExcelFile de Pandas tiene un mejor rendimiento cuando se trabaja con varias hojas de cálculo
de un chero Excel, ya que el chero se carga en memoria una sola vez. En el siguiente ejemplo,
mostramos cómo se lee un libro de Microsoft Excel mediante la función pd.ExcelFile. Posteriormente,
mediante el método to_excel creamos tantos dataframes como hojas de cálculo queramos leer.

libro = pd.ExcelFile('./datos/clientes.xlsx')
df1 = pd.read_excel(libro, sheet_name= 'Hoja1', index_col = [0])
df2 = pd.read_excel(libro, sheet_name= 'Hoja2')
df1

NOMCLI SEXO

CODCLI

CLI-0001 ROSA ELVIRA F

CLI-0003 MIGUEL ANGEL M

CLI-0004 ANA MARIA F

CLI-0013 BRIGGITTE F

CLI-0018 SAULO ANDRE M

CLI-0020 MONICA F

df2

CODCLI EUROS MAX

0 CLI-0001 89574 100000

1 CLI-0019 68752 80000

2 CLI-0020 63014 70000

Escritura en icheros Excel

Pandas también ofrece la posibilidad de escribir los datos incluídos en un DataFrame en una hoja de un
chero Excel. Para ello usamos el método to_excel de los DataFrame indicando el nombre del chero.
Los argumentos header e index , discutidos en el caso del método to_csv, también están disponibles
aquí.

dfexcel.to_excel('clientes4.xlsx', index = False)

Se puede observar que los valores NAN del dataframe pasan a ser celdas vacías en la hoja de cálculo,
pudiéndose usar el argumento na_rep para cambiar el comportamiento.
En el siguiente ejemplo usamos el argumento sheet_name para asignar un nombre a la hoja de cálculo
creada entro del chero y el argumento columns para indicar el subconjunto de columnas que queremos
llevar a la hoja de cálculo.

dfexcel.to_excel('clientes5.xlsx', index = False,


sheet_name='mis datos',
columns = ['NOMCLI', 'SEXO'],
na_rep='--')

El método to_excel tiene ciertas limitaciones. Por ejemplo, cuando queremos escribir los datos de
distintos dataframes en distintas hojas de cálculo dentro del mismo libro. La clase ExcelWriter de
Pandas resuelve éste problema.

En el siguiente ejemplo, mostramos cómo se crea un libro de Microsoft Excel mediante la función
pd.ExcelWriter. Posteriormente, mediante el método to_excel escribimos los datos de distintos
dataframes en distintas hojas, dentro del mismo chero.
df1 = dfexcel.loc[:, ['NOMCLI', 'SEXO']]
df1

NOMCLI SEXO

0 ROSA ELVIRA F

1 MIGUEL ANGEL NaN

2 ANA MARIA NaN

3 BRIGGITTE F

4 SAULO ANDRE M

5 NaN F

df2 = dfexcel.loc[:, ['CODCLI', 'TLF']]


df2

CODCLI TLF

0 CLI-0001 7578.0

1 CLI-0003 NaN

2 CLI-0004 NaN

3 CLI-0013 NaN

4 CLI-0018 NaN

5 CLI-0020 7600.0

libro = pd.ExcelWriter('clientes6.xlsx')
df1.to_excel(libro,'Datos en df1', index = False)
df2.to_excel(libro,'Datos en df2', index = False)
libro.save()
Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python for Data Analysis
Sección 5.2

Tratamento de icheros en formato HTML

Hoy en día existen muchos datos alojados en sitios web. En algunos casos, los datos se encuentran en
cheros (CSV o Excel) y pueden ser descargados libremente para su uso posterior. En otros casos, los
datos están integrados en la propia página a través de su código HTML, por lo que resulta muy útil
disponer de herramientas capaces de navegar por dicho código en busca de los datos que nos interesen.

La librería Pandas proporciona herramientas e cientes para manejar documentos en formato HTML. El
método to_html de la clase DataFrame permite transformar dataframes en tablas HTML de forma
automática. La operación inversa también es posible; la función pd.read_html es capaz de recorrer el
código HTML de una página web en busca de los datos contenidos en tablas.

Leer datos de un documento HTML

La función pd.read_html de Pandas recorre un documento HTML en busca de elementos de tipo


<table>. El valor devuelto por pd.read_html es una lista de objetos de tipo DataFrame, uno por cada
tabla encontrada en el documento. Como argumento de entrada, la función pd.read_html recibe un
documento HTML alojado localmente o un objeto de tipo str que represente el contenido de un
documento HTML.

En el siguiente ejemplo, mostramos cómo extraer la información de la página web con URL
"https://es.wikipedia.org/wiki/Anexo:Municipios_de_la_Comunidad_de_Madrid". Esta página muestra
datos asociados a distintas localidades de la Comunidad de Madrid. Para poder acceder al código HTML
de una página web es necesario realizar una petición usando el protocolo HTTP Request/Response.

import requests
import pandas as pd

url = "https://es.wikipedia.org/wiki/Anexo:Municipios_de_la_Comunidad_de_Madrid"
respuesta = requests.get(url)
if respuesta.status_code == 200:
print('La petición HTTP ha ido bien')
else:
print('Problemas con la petición...')

La petición HTTP ha ido bien

La librería Requests permite realizar peticiones HTTP de forma muy sentilla. Para usarla es necesario
importarla. La función requests.get abre una conexión con el servidor donde se encuentra la url y
manda la petición. La respuesta del servidor se almacena en un objeto de la clase Response. El objeto
devuelto almacena muchos datos relacionados con la petición HTTP (cabeceras, cookies, etc.). De
momento nos interesa conocer si la petición ha tenido éxito (propiedad status_code) y el string que
representa el documento HTML de la página (propiedad text).

codigoHTML = respuesta.text
lista_dataframes = pd.read_html(codigoHTML, header=0)

La función pd.read_html devuelve una lista de dataframes, en el caso de que la página asociada a la url
contenga algún elemento de tipo <table>. En otro caso, la función no devuelve la lista vacía, sino que
devuelve un error de ejecución.

len(lista_dataframes)

En nuestro ejemplo, la lista devuelta contiene un único dataframe:

lista_dataframes[0].head(7)

Nombre Población(2017) Super cie (km²)[1] Mapa Escudo Altitud(msnm)[a][2]

0 La Acebeda 66 2206 NaN NaN 1271

1 Ajalvir 4455 1962 NaN NaN 680

2 Alameda del Valle 200 2501 NaN NaN 1104

3 El Álamo 9149 2225 NaN NaN 608

4 Alcalá de Henares 194 310 8772 NaN NaN 587

5 Alcobendas 114 864 4498 NaN NaN 669

6 Alcorcón 168 141 3373 NaN NaN 711

Como se puede observar, la función pd.read_html ignora todos los elementos contenidos en el
documento HTML que no tengan nada que ver con tablas HTML.

Escritura de dataframes en código HTML

Una tabla HTML viene representada por una estructura jerárquica donde el elemento principal es el
elemento <table>. Cada la de la tabla se representa con un elemento de tipo <tr> y cada celda es un
elemento de tipo <th> para las celdas de cabecera y <td> para el resto. El siguiente ejemplo muestra el
código de una tabla HTML:
from IPython.display import HTML
s = """
<table>
<tr>
<th><strong>Ana</strong></th>
<th><strong>Pablo</strong></th>
<th><strong>Jaime</strong></th>
</tr>

<tr>
<td>10</td>
<td>20</td>
<td>30</td>
</tr>

<tr>
<td>40</td>
<td>50</td>
<td>60</td>
</tr>
</table>"""
HTML(s)

Ana Pablo Jaime

10 20 30

40 50 60

El método to_html de la clase DataFrame es capaza de crear una tabla HTML a partir de los datos
contenidos en el dataframe de forma automática.

dfcsv = pd.read_csv('datos/peliculas.csv', skiprows = [0, 1, 2, 3, 4 , 5, 6, 7, 8, 9])


dfcsv

1992 The Crying Game War

0 1995 Toy Story Animation

1 1995 GoldenEye Action

2 1995 Four Rooms Thriller


print(dfcsv.to_html())

<table border="1" class="dataframe">


<thead>
<tr style="text-align: right;">
<th></th>
<th>1992</th>
<th>The Crying Game</th>
<th>War</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>1995</td>
<td>Toy Story</td>
<td>Animation</td>
</tr>
<tr>
<th>1</th>
<td>1995</td>
<td>GoldenEye</td>
<td>Action</td>
</tr>
<tr>
<th>2</th>
<td>1995</td>
<td>Four Rooms</td>
<td>Thriller</td>
</tr>
</tbody>
</table>

Una vez generada la tabla HTML, podemos integrar el código en cualquier documento HTML para ser
interpretado por un navegador.

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python for Data Analysis
librería request
lxml
Sección 5.3

Datos en Formato JSON

Otro de los estándar para el intercambio de datos en la Webes el formato JSON (JavaScript Object
Notation). Al igual que ocurría con XML, permite representar cualquier estructura de datos de una forma
jerárquica, la cual puede ser fácilmente representada en forma de árbol.

Pandas dispone de la función pd.read_json() para crear dataframes a partir de un documento JSON.
La operación recíproca, el método to_json() de la clase DataFrame que permite guardar dataframes
en formato JSON.

Para entender cómo navegar por este tipo de documentos es necesario conocer su estructura interna.

Escritura de icheros JSON

Para empezar, veremos cómo crear un documento en formato JSON a partir de un dataframe usando el
método to_json.

import pandas as pd
import json

tabla = pd.DataFrame( [ (1982,"E.T. the ExtraTerrestrial","Fantasy"),


(1982,"Poltergeist","Horror"),
(1992,"Alien","Action"),
(1992,"The Crying Game","War")],
columns = ["Extreno","Nombre","Cat"])
tabla

Extreno Nombre Cat

0 1982 E.T. the ExtraTerrestrial Fantasy

1 1982 Poltergeist Horror

2 1992 Alien Action

3 1992 The Crying Game War

tabla.to_json('peliculas.json')
El documento peliculas.json se muestra a continuación:

peliculas.json
------------------

{
"Cat": {
"3": "War",
"2": "Action",
"0": "Fantasy",
"1": "Horror"
},
"Nombre": {
"3": "The Crying Game",
"2": "Alien",
"0": "E.T. the ExtraTerrestrial",
"1": "Poltergeist"
},
"Extreno": {
"3": 1992,
"2": 1992,
"0": 1982,
"1": 1982
}
}

Lectura de icheros JSON

La función pd.read_json permite realizar la operación inversa. Dicha función recibe como argumento la
ruta del chero que contiene los datos en formato JSON.
tabla = pd.read_json('./peliculas.json')
tabla

Extreno Nombre Cat

0 1982 E.T. the ExtraTerrestrial Fantasy

1 1982 Poltergeist Horror

2 1992 Alien Action

3 1992 The Crying Game War

En este caso, el proceso es muy simple porque el contenido del chero peliculas.json tenía una
estructura tabular, pero eso no es lo normal. La siguiente gura muestra el contenido del chero
canciones.json.
canciones.json
--------------

[
{
"Songs": [
{
"Length": "6:29",
"Date": 1992,
"Title": "Nothing Else Matters"
},
{
"Length": "4:21",
"Title": "Hero Of The Day"
}
],
"Genre": "Metal",
"Group": {
"Name": "Metallica"
}
},
{
"Songs": [
{
"Length": "4:37",
"Date": 2010,
"Title": "Need you now"
},
{
"Length": "4:17",
"Date": 2010,
"Title": "I Run To You"
},
{
"Length": "3:44",
"Title": "American honey"
}
],
"Genre": "Country pop",
"Group": {
"Name": "Lady Antebellum"
}
}
]

La función json_normalize de la sublibrería Pandas aporta un poco más de potencia. Por ejemplo, para
crear un dataframe con la información de las canciones, escribimo lo siguiente:

with open('./datos/canciones.json') as json_data:


d = json.load(json_data)
pd.json_normalize(d, "Songs")

Title Length Date

0 Nothing Else Matters 6:29 1992.0

1 Hero Of The Day 4:21 NaN

2 Need you now 4:37 2010.0

3 I Run To You 4:17 2010.0

4 American honey 3:44 NaN

La función json_normalize recibe como argumentos el objeto de la clase json y la clave del documento
que contiene los datos que buscamos. Si además queremos añadir al dataframe la información del grupo
musical al que pertenece cada canción, tenemos que añadir un tercer argumento que indica la lista de
claves que queremos añadir.

with open('./datos/canciones.json') as json_data:


d = json.load(json_data)
pd.json_normalize(d, "Songs", [["Group", "Name"], "Genre"])

Title Length Date Group.Name Genre

0 Nothing Else Matters 6:29 1992.0 Metallica Metal

1 Hero Of The Day 4:21 NaN Metallica Metal

2 Need you now 4:37 2010.0 Lady Antebellum Country pop

3 I Run To You 4:17 2010.0 Lady Antebellum Country pop

4 American honey 3:44 NaN Lady Antebellum Country pop

Referencias
Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python for Data Analysis
librería request
lxml
Sección 5.4

Acceso a Bases de datos Relacionales

Esta Sección tiene como objetivo mostrar las facilidades de Python


para conectarse con bases de datos externas. Sin embargo para
poder probar y reproducir éstos u otros ejemplos similares es
necesario tener conocimientos previos de bases de datos (y en
particular de MySQL y el lenguaje SQL), asi como tener acceso a una
base de datos MySQL (usuario/contraseña/puerto/host).

Por tanto, su lectura es opcional.

La gestión de la información mediante estructuras de cheros no es siempre la forma más adecuada de


almacenamiento de datos. Es por todos conocido que se pierden propiedades deseables como son, la
integridad, la no redundancia, etc. Son muchas las aplicaciones que usan bases de datos como forma de
almacenamiento, ya sean bases de datos relacionales, también conocidas como bases de datos SQL,
como las aparecidas recientemente no relacionales o NoSQL.

En esta sección mostramos cómo interacciona Pandas con la base de datos relacional MySQL.

En Python existe un mecanismo estándar para el acceso a bases de datos relacionales. Mostramos el
procedimiento a seguir para realizar la conexión, lectura y escritura de datos desde Python. Este
procedimiento será similar para otros sistemas gestores de bases de datos, y lo único que podrá cambiar
será la cadena de conexión.

Para el caso de bases de datos SQL utilizaremos el módulo sqlalchemy que proporciona las
herramientas necesarias para gestionar varios tipos de bases de datos (lo que SQLAlchemy denomina
dialecto).
MySQL

A continuación mostramos cómo establecer la conexión con una base de datos MySQL. En primer lugar
tenemos que importar el método create_engine disponible en el módulo sqlalchemy. Dicho método
establece los parámetros del tipo de base de datos concreta con la queremos trabajar (MySQL,
PostgreSQL, Microsoft SQL Server, Oracle, etc.) y los parámetros de conexión (usuario, contraseña, host y
puerto, etc).

Además, en el caso de MySQL, necesitamos instalar el módulo mysql(no viene en Anaconda), para poder
realizar la conexión.

conda install -c conda-forge mysql-connector-python

from sqlalchemy import create_engine


import pandas as pd

# parámetros de conexión
host_port='127.0.0.1:3306'
database='sakila'
user_pass='root' # user:pass
parametros ='mysql+mysqlconnector://'+ user_pass + '@' + host_port + '/' + database
parametros

'mysql+mysqlconnector://[email protected]:3306/sakila'

engineMySQL = create_engine(parametros)

Consultas

La creación de un dataframe a partir de los datos almacenados en una base de datos relacional es muy
simple utilizando las funciones que proporciona Pandas. La función read_sql permite ejecutar una
consulta SQL pasada como argumento. El resultado, será un objeto de la clase DataFrame, con tantas
columnas como atributos haya en la sentencia select y tantas tuplas como las devueltas por la
consulta.

En el siguiente ejemplo preguntamos acerca de las tablas existentes en la base de datos sakila.
query = 'SELECT table_name \
FROM information_schema.tables \
WHERE table_schema = "sakila";'

tables = pd.read_sql(query , con = engineMySQL)


tables

table_name

0 actor

1 address

2 category

3 city

4 country

5 customer

6 lm

7 lm_actor

8 lm_category

9 lm_text

10 sample_table2

11 tabla_nueva

Podemos preguntar por las columnas de una tabla:


query = 'SELECT column_name, data_type \
FROM information_schema.columns \
WHERE table_schema = "sakila" \
AND table_name = "film";'

col = pd.read_sql(query , con = engineMySQL)


col

column_name data_type

0 lm_id int

1 title varchar

2 description varchar

3 release_year varchar

4 language_id int

5 original_language_id int

6 rental_duration int

7 rental_rate varchar

8 length int

9 replacement_cost varchar

10 rating varchar

11 special_features varchar

12 last_update varchar

Creamos la consulta para seleccionar el título de las películas y su categoría


query = 'SELECT title, name \
FROM category , film, film_category \
where category.category_id = film_category.category_id \
and film.film_id = film_category.category_id; '

categoria = pd.read_sql(query, con=engineMySQL)


categoria.head()

title name

0 AGENT TRUMAN Documentary

1 ALAMO VIDEOTAPE Horror

2 AGENT TRUMAN Documentary

3 ALAMO VIDEOTAPE Horror

4 AIRPORT POLLOCK Family

Crear tablas

La operación inversa está a cargo del método to_sql de la clase DataFrame.

categoria.to_sql(name='tabla_nueva', con=engineMySQL,
if_exists = 'append', index=False)

El método to_sql recibe como argumento el nombre de la tabla de la base de datos donde se insertarán
las tuplas del dataframe df. El argumento if_exists permite indicar que las las ya existentes en la
tabla han de añadirse o reemplazarse.

Sentencias Insert

A partir del motor creamos un objeto de tipo conexión. Este objeto nos permitirá ejecutar consultas SQL
de todo tipo (insert, delete, drop, select, etc.) mediante la función execute.

consulta_insert = 'INSERT INTO tabla_nueva VALUES( %s , %s )'

connn = engineMySQL.connect() # creamos un objeto de tipo conexión para ejecutar una consulta ins
ert
connn.execute(consulta_insert, 'Drácula', 'Horror')
connn.close() # cerramosla conexión

Delete

consulta_delete = "DELETE FROM tabla_nueva \


WHERE title ='Drácula' ;"

connn = engineMySQL.connect() # creamos un objeto de tipo conexión para ejecutar una consulta ins
ert
connn.execute(consulta_delete)
connn.close()

Referencias
Python for Data Analysis
SQL conector
El módulo sqlAlchemy - The Python SQL Toolkit
SQL Server
Connecting to Microsoft Access
Sección 5.5

Ejercicios

El dataset BlackFriday.csv contiene 550.000 registros sobre las ventas realizadas en un comercio.
Cada registro contiene información del cliente (sexo, si está casado o no, edad, etc.) e información de la
compra que ha realizado en el comercio (importe de la compra, tipo de producto, etc.).

User_ID : Código de usuario


Product_ID : Código de producto
Sexo : Hombre(M), Mujer(F)
Edad : Franja de edad del usuario
Occupation
Cat_ciudad : Categoría de la ciudad de residencia del usuario
Years_en _la_ciudad : Número de años que el usario lleva residiendo en la ciudad
Estado_civil : Casado(1), soltero(0)
Product_Category_1
Product_Category_2
Product_Category_3
Imp_Compra : Importe o precio del producto

1.

Carga los datos del dataset BlackFriday.csv en un dataframe llamado ventas .

import pandas as pd

2.

Calcula los primeros estadísticos mínimo, máximo, media y desviación típica del importe de compra
Imp_Compra.

3.

¿Cuántas unidades se ha vendido de cada producto? Usa la operación value_counts().


4.

Sustituye los valores NaN de la columna Product_Category_2 por el valor 'Desconocido'.

¿Cuantos valores NaN hay en la columna Product_Category_3? Usa la operación isnull.

Selecciona las las de las compras realizadas por hombre solteros. Guarda el resultado de la selección
anterior en otro DataFrame llamado compras_solteros.

Selecciona a los usuarios que han realizado alguna compra con importe por debajo de la media.

Añadir una nueva columna al dataframe data llamada Imp+IVA que contenga el importe total de la
compra con IVA (Importe de la compra más el 21% de IVA).

Calcula el número de compras realizadas por mujeres y el número de compras realizadas por hombres.

10

Calcula la media del importe. Crear una nueva columna en el dataframe ventas llamada 'Tipo_compra'
cuyo valor sea 'Low' si el importe de la compra está por debajo de la media y 'High' si el valor de la
compra es mayor que la media. Usa la operación map sobre la columna ìmporte para aplicar una función
lambda que haga la transformación.
Sección 6.1

Combinar DataFrames

Cuando la información está distribuía en varios dataframes, antes de proceder al análisis es necesario
combinarlos para crear nuevos dataframes con toda la información necesaria. Existen varias
herramientas en Pandas para combinar dataframes. Veamos algunas de ellas junto con algunos ejemplos
ilustrativos de su uso.

Combinar tablas al estilo del álgebra relacional

La función pd.merge permite combinar las las de dos dataframes en función del valor de una o varias
columnas. Se trata de la operación join al estilo del álgebra relacional. Las columnas de combinación se
indican mediante una lista en el argumento on de la función. El argumento how de la función pd.merge
permite indicar el tipo de combinación que queremos hacer (left, right, outer o inner). El resultado
es un nuevo dataframe.
Supongamos que por un lado tenemos información de la cantidad de producción de ciertos productos en
distintos paises européos, y por otro lado la información de esos productos. Los datos se muestran en
dos dataframes t1 y t2.

import pandas as pd

d1 ={'País' : ['Estonia','Estonia', 'Ireland', 'Spain'],


'Cantidad' : [ 8.4, 6.7 , 227, 58.9 ] ,
'Producto' : ['A', 'B', 'A', 'Z'] }

d2 ={'Producto' : ['A', 'B', 'C'],


'Descripción' : [ 'Leche', 'Cereales', 'Aceite' ] }
t1 = pd.DataFrame( d1 )
t1

Cantidad País Producto

0 8.4 Estonia A

1 6.7 Estonia B

2 227.0 Ireland A

3 58.9 Spain Z

t2 = pd.DataFrame( d2 )
t2

Descripción Producto

0 Leche A

1 Cereales B

2 Aceite C

Por ejemplo, la función pd.merge permite combinar los dataframes t1 y t2 usando como campos de
combinación la columna Producto. Veamos ejemplos con distintas opciones:

result = pd.merge(t1, t2, on = ['Producto'])


result

Cantidad País Producto Descripción

0 8.4 Estonia A Leche

1 227.0 Ireland A Leche

2 6.7 Estonia B Cereales

Por defecto la función pd.merge realiza la operación inner. La opción inner combina los dataframes
devolviendo únicamente aquellas las que tienen valores idénticos en las columnas que se comparan.

result = pd.merge(t1, t2, on=['Producto'], how = 'inner')


result

Cantidad País Producto Descripción

0 8.4 Estonia A Leche

1 227.0 Ireland A Leche

2 6.7 Estonia B Cereales


En el ejemplo anterior, podemos observar que desaparecen las las donde el valor de la columna
Producto tiene valores C y Z.

La opción left combina dos dataframes devolviendo aquellas las que tienen valores idénticos en las
columnas que se comparan para unir ambas tablas, y todas las las del dataframe de la izquierda, tengan
o no correspondencia con las las del dataframe de la derecha. Las que no tengan correspondencia se
rellenan con NaN.

result = pd.merge(t1, t2, on=['Producto'], how='left')


result

Cantidad País Producto Descripción

0 8.4 Estonia A Leche

1 6.7 Estonia B Cereales

2 227.0 Ireland A Leche

3 58.9 Spain Z NaN

En este caso, no existe el valor Z en la columna Producto del dataframe de la derecha, por lo que el valor
de la columna Descripción pasa a ser NaN.
Análogamente, la opción right combina dos dataframes devolviendo como resultado aquellas las que
tienen valores idénticos en las columnas que se comparan para unir ambas datframes, y todas las las
del dataframe de la derecha, tengan o no correspondencia con las las del dataframe de la izquierda. Las
que no tengan correspondencia se rellenan con NaN.

result = pd.merge(t1, t2, how='right', on=['Producto'])


result

Cantidad País Producto Descripción

0 8.4 Estonia A Leche

1 227.0 Ireland A Leche

2 6.7 Estonia B Cereales

3 NaN NaN C Aceite

La opción outer combina los dos datframes devolviendo la unión de las las devueltas por la opción
left y right:
result = pd.merge(t1, t2, how='outer', on=['Producto'])
result

Cantidad País Producto Descripción

0 8.4 Estonia A Leche

1 227.0 Ireland A Leche

2 6.7 Estonia B Cereales

3 58.9 Spain Z NaN

4 NaN NaN C Aceite

La función pd.merge es muy rica en funcionalidad y los resultados dependen del valor de sus
argumentos. Con lo visto anteriormente, podríamos suponer que para poder combinar dos dataframes
es necesario que las columnas de combinación se llamen igual. Veamos que esto no es así.

d3 ={'Id.Producto' : ['A', 'B', 'C'],


'Descripción' : [ 'Leche', 'Cereales', 'Aceite' ] }
t3 = pd.DataFrame( d3 )
t3

Descripción Id.Producto

0 Leche A

1 Cereales B

2 Aceite C

Usamos los argumentos left_on y right_on en lugar de on para indicar las columnas de combinación
de los dataframes de la izquierda y derecha respectivamente:

result = pd.merge(t1, t3, left_on = 'Producto', right_on = 'Id.Producto')


result

Cantidad País Producto Descripción Id.Producto

0 8.4 Estonia A Leche A

1 227.0 Ireland A Leche A

2 6.7 Estonia B Cereales B

Combinar sobre índices


En algunos casos, el valor de comparación no se encuentra en las columnas, sino en el índice del
dataframe.

Supongamos que tenemos los dataframes t4 y t5:

t4 = pd.DataFrame( data = [ (92, 89),


(230, 231),
(201 , 193),
(144, 137) ],
columns = [ 'Densidad 2005', 'Densidad 2015'],
index = [ 'ES', 'AL', 'IT', 'CH'])
t4

Densidad 2005 Densidad 2015

ES 92 89

AL 230 231

IT 201 193

CH 144 137

t5 = pd.DataFrame( data = [ ('España', 46449),


('Alemania',81197),
('Japón',127120) ],
columns = ['País', 'Pob 2015'],
index = [ 'ES', 'AL', 'JP'])
t5

País Pob 2015

ES España 46449

AL Alemania 81197

JP Japón 127120

Si queremos combinar ambos dataframes usando el valor del índice de ambos, usaremos los argumentos
left_index y right_index como se muestra a continuación:

result = pd.merge(t4, t5, how = 'left', left_index = True, right_index = True)


result

Densidad 2005 Densidad 2015 País Pob 2015

ES 92 89 España 46449.0

AL 230 231 Alemania 81197.0

IT 201 193 NaN NaN

CH 144 137 NaN NaN


También podemos combinar los dataframes indicando una columna de combinación de un dataframe y
el índice del otro dataframe:

t6 = pd.DataFrame( data = [ ('España', 'ES', 46449),


('Alemania', 'AL' , 81197),
('Japón', 'JP', 127120) ],
columns = ['País', 'Código', 'Pob 2015'])
t6

País Código Pob 2015

0 España ES 46449

1 Alemania AL 81197

2 Japón JP 127120

result = pd.merge(t6, t4, how = 'left', left_on = 'Código', right_index = True)


result

País Código Pob 2015 Densidad 2005 Densidad 2015

0 España ES 46449 92.0 89.0

1 Alemania AL 81197 230.0 231.0

2 Japón JP 127120 NaN NaN

Cuando la combinación de dataframes se realiza en base a los índices, resulta más apropiado utilizar el
método join de los DataFrame. El único requisito para poder utilizar esta operación es que los
dataframes involucrados no tengan columnas con el mismo nombre.

t4.join(t5)

Densidad 2005 Densidad 2015 País Pob 2015

ES 92 89 España 46449.0

AL 230 231 Alemania 81197.0

IT 201 193 NaN NaN

CH 144 137 NaN NaN

Concatenar en base a un eje

La función pd.concat de Pandas permite construir nuevos objetos de tipo Series o Dataframe en base
a uno de los ejes. Ya vimos las funciones hstack y vstack de nidas en la librería Numpy para apilar
arrays horizontal y verticalmente respectivamente. En el caso de Pandas es un poco más complicado, ya
que no podemos olvidar que tenemos etiquetas, tanto para las las como para las columnas.

Veamos algunos ejemplos que muestren el comportamiento de la función pd.concat, tanto para series
como para dataframes. Supongamos que tenemos dos series s1y s2 con índices disjuntos:

s1 = pd.Series( [89, 231, 336],


index = [ 'ES', 'AL', 'JP'])
s1

ES 89
AL 231
JP 336
dtype: int64

s2 = pd.Series( [201 , 144],


index = [ 'IT', 'CH'])
s2

IT 201
CH 144
dtype: int64

En este caso, podemos usar la función pd.concat para apilar verticalmente tanto los índices como los
valores. Para ello usaremos el argumento axis con valor 0:

result = pd.concat([s1, s2], axis = 0)


result

ES 89
AL 231
JP 336
IT 201
CH 144
dtype: int64

También podemos apilar horizontalmente usando el argumento axis con valor 1:

result = pd.concat([s1, s2], axis = 1)


result

0 1

AL 231.0 NaN

CH NaN 144.0

ES 89.0 NaN

IT NaN 201.0

JP 336.0 NaN

Como podemos observar, en ambos casos se produce la unión de los índices, ya que la interseción de los
índices es vacía.

Pongamos ahora el caso de que la intersección de índice no sea vacía. De nimos una tercera serie s3, de
forma que las series s1y s3 tienen el índice común ES.
s3 = pd.Series( [100, 201 , 144],
index = [ 'ES', 'IT', 'CH'])
s3

ES 100
IT 201
CH 144
dtype: int64

result = pd.concat([s1, s3], axis = 1)


result

0 1

AL 231.0 NaN

CH NaN 144.0

ES 89.0 100.0

IT NaN 201.0

JP 336.0 NaN

El siguiente código permite apilar horizontalmente añadiendo el nombre a las columnas. Para ello hemos
usado el argumento keys:

result = pd.concat([s1, s3], axis = 1, keys = ['Col 1', 'Col 2'])


result

Col 1 Col 2

AL 231.0 NaN

CH NaN 144.0

ES 89.0 100.0

IT NaN 201.0

JP 336.0 NaN

Para el caso de objetos de tipo DataFrame la mecánica es similar. En el siguiente ejemplo creamos dos
dataframes df1 y df2 :

d1 ={'País' : ['Estonia', 'Irlanda', 'Epaña'],


'Cantidad' : [ 8.4, 227, 58.9 ] }

d2 ={'Producto' : ['A', 'B'],


'Descripción' : [ 'Leche', 'Cereales', ] }

df1 = pd.DataFrame(d1, index = ['ES', 'IR', 'SP'])


df2 = pd.DataFrame(d2, index = ['ES', 'IR'])
df1

Cantidad País

ES 8.4 Estonia

IR 227.0 Irlanda

SP 58.9 Epaña

df2

Descripción Producto

ES Leche A

IR Cereales B

Para apilar ambos dataframes horizontamente, escribimos:

result = pd.concat([df1, df2] , axis = 1)


result

Cantidad País Descripción Producto

ES 8.4 Estonia Leche A

IR 227.0 Irlanda Cereales B

SP 58.9 Epaña NaN NaN

Para apilar verticalmente, escribimos:

result = pd.concat([df1, df2] , axis = 0)


result

Cantidad Descripción País Producto

ES 8.4 NaN Estonia NaN

IR 227.0 NaN Irlanda NaN

SP 58.9 NaN Epaña NaN

ES NaN Leche NaN A

IR NaN Cereales NaN B

El argumento ignore_index permite apilar los dataframes ignorando el valor de los índices:
result = pd.concat([df1, df2] , axis = 0, ignore_index = True)
result

Cantidad Descripción País Producto

0 8.4 NaN Estonia NaN

1 227.0 NaN Irlanda NaN

2 58.9 NaN Epaña NaN

3 NaN Leche NaN A

4 NaN Cereales NaN B

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python Data Analysis Library
Python for Data Analysis
Sección 6.2

Preparación y limpieza de los datos

Una gran parte del tiempo empleado en la tareas de análisis de datos se invierte en la preparación de los
mismos. En la mayoría de las ocasiones, nuestras aplicaciones y desarrollos necesitan que los datos
tengan un determinado formato. Sin embargo, los datos que se encuentran en cheros o en bases de
datos, no tienen por qué cumplir, a priori, nuestros requisitos, por lo que será necesario proceder a su
limpieza, transformación, procesamiento, etc.

Importación de librerías

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

%matplotlib inline

Operación Pivot

La operación pivot permite construir un nuevo dataframe a partir de otro, de forma que los valores de
una columna pasen a ser nombres de columna del nuevo dataframe. Esta operación se usa
habitualmente cuando se trabaja con bases de datos relacionales y tiene la misma funcionalidad que en
otros lenguajes de análisis de datos. Lo veremos más claro con un ejemplo.
El siguiente dataframe recoge la información de varios pedidos.
datos = np.array([[101, 'p1', 2],
[101, 'p2', 3],
[101, 'p3', 4],
[102, 'p3', 1],
[102, 'p2', 2],
[102, 'p4', 5],
[103, 'p2', 1],
[103, 'p1', 3],
[103, 'p4', 2]])
pedidos = pd.DataFrame(datos,
columns = ['Número_pedido', 'Producto', 'Cantidad'])
pedidos

Número_pedido Producto Cantidad

0 101 p1 2

1 101 p2 3

2 101 p3 4

3 102 p3 1

4 102 p2 2

5 102 p4 5

6 103 p2 1

7 103 p1 3

8 103 p4 2

Nótese que asociado a cada pedido tenemos varios productos, cada uno de ellos con la cantidad pedida.

El método pivot de los Dataframe permite girar los datos y se invoca con al menos tres argumentos. El
primer argumento index tiene como valor el nombre de la columna cuyos valores contituirán las
etiquetas del índice del nuevo dataframe. El segundo argumento columns es el nombre de la columna
cuyos valores constituirán las etiquetas del índice de las columnas del nuevo dataframe. Por último, el
tercer argumento values es el nombre de la columna cuyos valores pasan a ser los valores del nuevo
Dataframe.
pedidos.pivot(index = 'Número_pedido', columns = 'Producto', values = 'Cantidad')

Producto p1 p2 p3 p4

Número_pedido

101 2 3 4 NaN

102 NaN 2 1 5

103 3 1 NaN 2

El método pivot exige que no existan dos las con los mismos valores para las columnas asociadas a los
dos primeros argumentos (index y columns). En este ejemplo, las columnas Número_pedido y
Producto forman lo que en bases de datos relacionales llamamos clave primaria, es decir, no existen dos
las con el mismo número de pedido y el mismo producto.

Si no se cumple la condicion, podemos usar el método pivot_table de los DataFrame. Su


funcionamiento es similar a pivot con la ventaja de que permite aplicar funciones de agregación.

datos = [[2001, 1, 'p1', 100],


[2001, 2, 'p1', 50],
[2001, 3, 'p2', 20],
[2004, 1, 'p3', 100],
[2004, 1, 'p1', 100],
[2004, 2, 'p1', 200],
[2007, 2, 'p1', 50],
[2007, 3, 'p1', 40]]

prod = pd.DataFrame(datos, columns = ['Ejercicio', 'Trimestre', 'Producto', 'Kg.'])


prod

Ejercicio Trimestre Producto Kg.

0 2001 1 p1 100

1 2001 2 p1 50

2 2001 3 p2 20

3 2004 1 p3 100

4 2004 1 p1 100

5 2004 2 p1 200

6 2007 2 p1 50

7 2007 3 p1 40
prod.pivot_table(index = 'Ejercicio', columns = 'Producto', values = 'Kg.', aggfunc = sum)

Producto p1 p2 p3

Ejercicio

2001 150.0 20.0 NaN

2004 300.0 NaN 100.0

2007 90.0 NaN NaN

En el ejemplo anterior, se aplica la función suma (sum), para sumar la cantidad de producto producida en
cada ejercicio.

El argumento fill_value permite reemplazar los valores NaN por un valor por defecto.

prod.pivot_table(index = 'Ejercicio', columns = 'Producto', values = 'Kg.',


aggfunc = sum, fill_value = 0)

Producto p1 p2 p3

Ejercicio

2001 150 20 0

2004 300 0 100

2007 90 0 0

Eliminación de duplicados

La detección de datos duplicados en dataframes supone un gran esfuerzo cuando se habla de grandes
cantidades de datos.

El método duplicated de los DataFrame permite identi car las repetidas. Como resultado se obtiene
una serie cuyos valores son de tipo bool, cada uno de los cuales está asociado a una la. El valor True
indica que ya existe una la anterior en el dataframe con los mismos valores (la primera aparición no se
considera repetida). En caso contrario, el valor será False. Consideremos el siguiente dataframe con las
repetidas:
t = pd.DataFrame([['Africa', 11506],
['Africa', 11506],
['Antarctica', 5500],
['Antarctica', 5500],
['Africa', 11506],
['Banks', 23],
['Africa', 10000]], columns = ['Nombre', 'Millas cuadradas'])
t

Nombre Millas cuadradas

0 Africa 11506

1 Africa 11506

2 Antarctica 5500

3 Antarctica 5500

4 Africa 11506

5 Banks 23

6 Africa 10000

t.duplicated()

0 False
1 True
2 False
3 True
4 True
5 False
6 False
dtype: bool

El resultado del método duplicated se puede utilizar como ltro para obtener un dataframe con las las
repetidas.

t[t.duplicated()]

Nombre Millas cuadradas

1 Africa 11506

3 Antarctica 5500

4 Africa 11506

Para eliminar las las repetidas de un dataframe utilizamos el método drop_duplicates de los
DataFrame.
t.drop_duplicates()

Nombre Millas cuadradas

0 Africa 11506

2 Antarctica 5500

5 Banks 23

6 Africa 10000

También nos puede interesar eliminar las si se repiten los valores de un subconjunto de columnas. En
ese caso es necesario indicar dicho subconjunto de columnas en el argumento subset.

t.drop_duplicates(subset = ['Nombre'])

Nombre Millas cuadradas

0 Africa 11506

2 Antarctica 5500

5 Banks 23

Por defecto se mantiene la primera la y se elimina el resto de las repetidas. El argumento keep con
valor last permite eliminar todas las las repetidas excepto la última aparición.

t.drop_duplicates(subset = ['Nombre'], keep = 'last')

Nombre Millas cuadradas

3 Antarctica 5500

5 Banks 23

6 Africa 10000

Aplicación de funciones

Funciones elemento a elemento

Al estar Pandas construída sobre la librería Numpy, las funciones universales de nidas en Numpy se
extienden para adaptarse a las nuevas estructuras de datos presentes en Pandas. Se trata de funciones
que se aplican a cada uno de los valores de la estructura, ya sea un objeto de tipo ndarray, Series o
DataFrame.
datos = [ ( 8, 7, 6),
(2, 4, 16),
(10,20,30),
(4,2,5),
]
t = pd.DataFrame( datos,
columns = [ '2004', '2005', '2006'],
index = [ 'Estonia', 'Ireland', 'Greece', 'Spain'])
t

2004 2005 2006

Estonia 8 7 6

Ireland 2 4 16

Greece 10 20 30

Spain 4 2 5

Por ejemplo, para aplicar la raiz cuadrada a cada valor de un dataframe, utilizamos la función np.sqrt de
Numpy.

np.sqrt(t)

2004 2005 2006

Estonia 2.828427 2.645751 2.449490

Ireland 1.414214 2.000000 4.000000

Greece 3.162278 4.472136 5.477226

Spain 2.000000 1.414214 2.236068

Para calcular el cuadrado de cada uno de los vlores de un dataframe, utilizamos la función np.square de
Numpy.

np.square(t)

2004 2005 2006

Estonia 64 49 36

Ireland 4 16 256

Greece 100 400 900

Spain 16 4 25

La aplicación de funciones a cada uno de los valores de una serie o un dataframe no se reduce a las
funciones universales de nidas en Numpy, si no que también es posible aplicar funciones de nidas por el
usuario. En el caso de objetos de tipo Series es necesario utilizar el método map para aplicar funciones
de usuario.

f1 = lambda x: x ** 2 - 2

s = pd.Series([2, 4, 6, 8 ], index = ['EE', 'IR', 'GR', 'ES'])


s

EE 2
IR 4
GR 6
ES 8
dtype: int64

s.map(f1)

EE 2
IR 14
GR 34
ES 62
dtype: int64

En el caso de objetos de tipo DataFrame usaremos el método applymap.

t.applymap(f1)

2004 2005 2006

Estonia 62 47 34

Ireland 2 14 254

Greece 98 398 898

Spain 14 2 23

La siguiente función de usuario multiplica por 10 aquellos valores menos que 100.

f2 = lambda x: x * 10 if x < 100 else x

Se puede utilizar la aplicación de funciones para crear nuevas columnas en un dataframe en base a los
datos de otra columna.
t['Nueva'] = t['2004'].map(f2)
t

2004 2005 2006 Nueva

Estonia 8 7 6 80

Ireland 2 4 16 20

Greece 10 20 30 100

Spain 4 2 5 40

Funciones aplicables a ilas o columnas

El método apply de los DataFrame permite aplicar funciones de usuario tanto a las como a columnas
de un dataframe.

f3 = lambda x: x.sum() - x.min()

t = pd.DataFrame( [[10, 20, 30],


[40, 10, 60],
[70, 80, 10]],
index = ['P1', 'P2', 'P3'],
columns = ['A', 'B', 'C'])
t

A B C

P1 10 20 30

P2 40 10 60

P3 70 80 10

t.apply(f3)

A 110
B 100
C 90
dtype: int64

El resultado es una serie, donde las etiquetas del índice son las etiquetas del índice de las columnas del
dataframe. Cada uno de los valores de la serie es el resultado de aplicar la función de usuario a cada
columna. El argumento axis permite aplicar la función a cada una de las las.

t.apply(f3, axis = 1)

P1 50
P2 100
P3 150
dtype: int64

La función de usuario puede devolver una serie en lugar de un valor escalar. Esto permitirá la aplicación
de varias funciones simultáneamente.

def f4(x):
return pd.Series([sum(x) , min(x), f3(x) ], index = ['Suma', 'Mínimo', 'f3'])

t.apply(f4, axis = 1)

Suma Mínimo f3

P1 60 10 50

P2 110 10 100

P3 160 10 150

Como podemos observar en el ejemplo anterior, el resultado es un dataframe con tantas columnas como
valores haya en la serie devuelta por la función de usuario.

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python Data Analysis Library (http://pandas.pydata.org/)
Python for Data Analysis (http://shop.oreilly.com/product/0636920023784.do)
Sección 6.3

Grupos y operaciones de agregación

Después de realizar la importación de datos y procesarlos, una de las tareas más habituales es la
agrupación de dichos datos en base a alguna característica, para posteriormente realizar operaciones
sobre cada uno de los grupos obtenidos. Ésto se realiza habitualmente en un único paso gracias al
método groupby de los DataFrame. Se trata de una operación con funcionalidad similar a la sentencia
group by del lenguaje SQL.

La operación Groupby

Por un lado, los datos se dividen en grupos en base a una o más características. Por ejemplo, es posible
hacer grupos de las (axis = 0) o hacer grupos de columnas (axis = 1). Una vez creados los grupos, es
posible realizar alguna operación con cada grupo. Por ejemplo, operaciones de agregación. Se aplica una
función (prede nida o de usuario) a cada uno de los grupos. El resultado es un valor para cada uno de los
grupos. También es posible realizar operaciones de transformación. Éstas se aplican a cada uno de los
grupos y se obtiene como resultado una serie para cada grupo.

Veamos algunos ejemplos. En primer lugar de nimos un dataframe con datos numéricos y datos de tipo
cadena.

import numpy as np
import pandas as pd

datos =[['Chile', 'South America', 'Dutch', 35],


['Burundi', 'Africa', 'French',2],
['China', 'Asia', 'Dutch', 62],
['Cuba', 'North America', 'Dutch', 40],
['Andorra', 'Europe', 'French', 6],
['China', 'Asia', 'Greek', 2],
['Burundi', 'Africa', 'French', 1],
['Belgium', 'Europe', 'Dutch', 50],
['Belgium', 'Europe', 'French', 32],
['Cuba', 'North America', 'Greek', 3]]
t = pd.DataFrame(datos, columns = ['País', 'Continente', 'Idioma', 'Población %'])
t

País Continente Idioma Población %

0 Chile South America Dutch 35

1 Burundi Africa French 2

2 China Asia Dutch 62

3 Cuba North America Dutch 40

4 Andorra Europe French 6

5 China Asia Greek 2

6 Burundi Africa French 1

7 Belgium Europe Dutch 50

8 Belgium Europe French 32

9 Cuba North America Greek 3

Supongamos que nos interesa conocer el número de idiomas que se habla en cada país. Una forma de
resolverlo es hacer tantos grupos como países y posteriormente contar el número de tuplas de cada
grupo. En primer lugar invocaremos al método groupby con la columna País.

g = t.groupby(['País'])
g

<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001EC57D8D550>

El resultado de la operación groupby es un objeto de tipo Groupby. Los objetos de tipo Groupby tiene
una serie de propiedades, por ejemplo la propiedad groups.

g.groups

{'Andorra': Int64Index([4], dtype='int64'),


'Belgium': Int64Index([7, 8], dtype='int64'),
'Burundi': Int64Index([1, 6], dtype='int64'),
'Chile': Int64Index([0], dtype='int64'),
'China': Int64Index([2, 5], dtype='int64'),
'Cuba': Int64Index([3, 9], dtype='int64')}

La propiedad ngroups indica el número de grupos en los que se ha dividido el dataframe original.

g.ngroups

6
El método size de los GroupBy devuelve como resultado una serie, donde el índice está formado por los
nombres de cada grupo y los valores asociados se corresponden con el tamaño del grupo.

g.size()

País
Andorra 1
Belgium 2
Burundi 2
Chile 1
China 2
Cuba 2
dtype: int64

Lo más habitual es realizar la operación de división en grupos y posteriormente aplicar alguna operación
de apregación. Por ejemplo, podemos aplicar la función de agregación count, que cuenta el número de
las del grupo.

País Continente Idioma Población %

0 Chile South America Dutch 35

1 Burundi Africa French 2

2 China Asia Dutch 62

3 Cuba North America Dutch 40

4 Andorra Europe French 6

5 China Asia Greek 2

6 Burundi Africa French 1

7 Belgium Europe Dutch 50

8 Belgium Europe French 32

9 Cuba North America Greek 3

t.groupby(['País']).Idioma.count()

País
Andorra 1
Belgium 2
Burundi 2
Chile 1
China 2
Cuba 2
Name: Idioma, dtype: int64

Para conocer el número de países en los que se habla un determinado idioma, será necesario agrupar los
datos por idioma y posteriormente aplicar la función de agregación count, lo que nos dará como
resultado el número de paises.
t.groupby(['Idioma']).País.count()

Idioma
Dutch 4
French 4
Greek 2
Name: País, dtype: int64

El resultado es una serie donde las etiquetas del índice son los valores del campo de agrupación.
Para continuar con los ejemplos, supongamos que tenemos un chero envios.csv con información de una
colección de envíos. De cada uno de ellos conocemos la fecha de entrega, la categoría, el importe, el peso
del paquete y un indicador de si es considerado urgente o no.

fact = pd.read_csv("./datos/envios.csv", index_col = [0], parse_dates = [0])


fact

Categoria Importe Peso Urgente

2006-06-20 P 0.90 0.99 Si

2006-10-17 P 4.40 0.20 Si

2006-06-23 M 0.10 2.70 Si

2006-06-24 M 2.70 1.50 Si

2006-06-27 M 2.80 0.34 Si

2006-06-25 G 0.70 1.32 Si

2006-06-21 P 0.40 0.21 No

2006-12-14 P 0.80 0.12 No

2006-06-22 G 4.20 0.40 No

2006-10-29 G 4.99 0.34 No

2006-06-26 G 2.99 1.10 No

Supongamos que deseamos conocer la media de Importe para cada Categoría. En este caso, debemos
agrupar por la columna Categoria, seleccionar la columna Importe, y posteriormente aplicar el método
mean que calcula la media.

fact.groupby(['Categoria']).Importe.mean()

Categoria
G 3.220000
M 1.866667
P 1.625000
Name: Importe, dtype: float64

Si deseamos calcular la media del importe para cada categoría dependiendo de si es urgente o no,
escribimos lo siguiente:
fact.groupby(['Categoria', 'Urgente']).Importe.mean()

Categoria Urgente
G No 4.060000
Si 0.700000
M Si 1.866667
P No 0.600000
Si 2.650000
Name: Importe, dtype: float64

En este caso, tenemos que agrupar por los valores de las columnas Categoria y Urgente. Como
podemos observar, el resultado es una serie con índice jerárquico. La función unstack devuelve como
resultado un dataframe con índice jarárquico para el índice de las columnas.

fact.groupby(['Categoria', 'Urgente']).Importe.mean().unstack()

Urgente No Si

Categoria

G 4.06 0.700000

M NaN 1.866667

P 0.60 2.650000

Iterar sobre grupos

Los objetos de tipo GroupBy permiten iteración, generando una secuencia de tuplas del tipo
(nombre_grupo, contenido).

g = fact.groupby(['Categoria', 'Urgente'])

for (nombre_grupo, contenido) in fact.groupby(['Categoria', 'Urgente']):


print(nombre_grupo)

('G', 'No')
('G', 'Si')
('M', 'Si')
('P', 'No')
('P', 'Si')

Los resultados nos indican que se han creado 5 grupos. El siguiente fragmento de código muestra el
contenido de cada uno de los grupos.
for (nombre_grupo, contenido) in fact.groupby(['Categoria', 'Urgente']):
print(contenido)

Categoria Importe Peso Urgente


2006-06-22 G 4.20 0.40 No
2006-10-29 G 4.99 0.34 No
2006-06-26 G 2.99 1.10 No
Categoria Importe Peso Urgente
2006-06-25 G 0.7 1.32 Si
Categoria Importe Peso Urgente
2006-06-23 M 0.1 2.70 Si
2006-06-24 M 2.7 1.50 Si
2006-06-27 M 2.8 0.34 Si
Categoria Importe Peso Urgente
2006-06-21 P 0.4 0.21 No
2006-12-14 P 0.8 0.12 No
Categoria Importe Peso Urgente
2006-06-20 P 0.9 0.99 Si
2006-10-17 P 4.4 0.20 Si

El contenido de cada uno de los grupos es un DataFrame.

type(contenido)

pandas.core.frame.DataFrame

Agrupando con funciones

Para hacer cosas más creativas podemos usar funciones para agrupar datos. Por ejemplo, es posible
aplicar una función a cada uno de los valores del índice y agrupar los datos en base al valor devuelto por
la función.
fact

Categoria Importe Peso Urgente

2006-06-20 P 0.90 0.99 Si

2006-10-17 P 4.40 0.20 Si

2006-06-23 M 0.10 2.70 Si

2006-06-24 M 2.70 1.50 Si

2006-06-27 M 2.80 0.34 Si

2006-06-25 G 0.70 1.32 Si

2006-06-21 P 0.40 0.21 No

2006-12-14 P 0.80 0.12 No

2006-06-22 G 4.20 0.40 No

2006-10-29 G 4.99 0.34 No

2006-06-26 G 2.99 1.10 No

Supongamos que nos interesa conocer el importe total facturado en cada día de la semana. Es decir, el
importe facturado los lunes, los martes, etc. La función de usuario calcular_dia devuelve como
resultado el día de la semana (valor numérico entre 0 y 6) de una fecha. En primer lugar, importamos la
librería date.

from datetime import date


def calcular_dia(f):
return f.weekday()

Por ejemplo, el día 26 de Junio del año 2006 fué lunes. La función calcular_dia devuelve el valor 0.

calcular_dia(date(2006,6,26))

El método groupby recibe como argumento la función de usuario calcular_dia. El resultado es un


objeto de tipo Groupby de tamaño 7.

r = fact.groupby(calcular_dia)
r.ngroups

7
for (dia, contenido) in r:
print(dia)
print(contenido)

0
Categoria Importe Peso Urgente
2006-06-26 G 2.99 1.1 No
1
Categoria Importe Peso Urgente
2006-06-20 P 0.9 0.99 Si
2006-10-17 P 4.4 0.20 Si
2006-06-27 M 2.8 0.34 Si
2
Categoria Importe Peso Urgente
2006-06-21 P 0.4 0.21 No
3
Categoria Importe Peso Urgente
2006-12-14 P 0.8 0.12 No
2006-06-22 G 4.2 0.40 No
4
Categoria Importe Peso Urgente
2006-06-23 M 0.1 2.7 Si
5
Categoria Importe Peso Urgente
2006-06-24 M 2.7 1.5 Si
6
Categoria Importe Peso Urgente
2006-06-25 G 0.70 1.32 Si
2006-10-29 G 4.99 0.34 No

Para conocer el importe total de cada grupo, aplicamos la función de agregación sum a la columna
Importe de cada uno de los grupos.

fact.groupby(calcular_dia).Importe.sum()

0 2.99
1 8.10
2 0.40
3 5.00
4 0.10
5 2.70
6 5.69
Name: Importe, dtype: float64

Agrupando por índice

Los valores por los que se desea agrupar, no siempre se correponden con valores en columnas del
dataframe. El argumento axis del método groupby permite indicar si la agrupación se realizará por
etiquetas en el índice (axis = 0), o por valores de columnas (axis = 1). Además, el argumento level
permite indicar el nivel de índice por el que se quiere agrupar.

datos = {'Producto': ['p1', 'p2', 'p3', 'p3', 'p2', 'p4', 'p2'],


'Cantidad': [2.0, 3.0, 4.0, 1.0, 2.0, 5.0, 1.0],
'Precio' : [1.0, 3.0, 2.0, 1.0, 4.0, 2.0, 3.0]}
pedidos = pd.DataFrame(datos,
columns = ['Producto', 'Cantidad', 'Precio'],
index = ['101', '101', '102', '102', '103', '103', '103']
)
pedidos

Producto Cantidad Precio

101 p1 2.0 1.0

101 p2 3.0 3.0

102 p3 4.0 2.0

102 p3 1.0 1.0

103 p2 2.0 4.0

103 p4 5.0 2.0

103 p2 1.0 3.0

El dataframe pedidos recoge información de codigo de pedido (etiquetas del índice de las las), el código
de producto, la cantidad de producto dentro de cada pedido y el precio.
Si queremos calcular el precio medio de cada pedido, escribimos lo siguiente:

pedidos.groupby(axis = 0, level = 0).Precio.mean()

101 2.0
102 1.5
103 3.0
Name: Precio, dtype: float64

Agrupamos a nivel de índice de las e indicamos el nivel. Posteriormente aplicamos la media (función
mean) a la columna Precio.

Funciones de agregación

Una vez que tenemos los datos divididos en grupos, las operaciones de agregación (mean, sum, count,
etc.) permiten realizar operaciones cuyo resultado es un único valor por grupo. También es posible
de nir funciones de usuario y utilizarlas una vez construidos los grupos. Para ello usamos la función agg.

Supongamos que queremos calcular el precio total de cada pedido menos el precio del producto más
barato.
pedidos

Producto Cantidad Precio

101 p1 2.0 1.0

101 p2 3.0 3.0

102 p3 4.0 2.0

102 p3 1.0 1.0

103 p2 2.0 4.0

103 p4 5.0 2.0

103 p2 1.0 3.0

En primer lugar de nimos la función que realiza el cálculo deseado. La función total_desc recibe como
argumento una serie, calcula la suma de los valores (método sum), el mínimo valor (método min) y
devuelve la diferencia de ambos valores.

def total_desc(datos):
minimo = datos.min()
total = datos.sum() - minimo
return total

Posteriormente, aplicamos la función de usuario total_desc como argumento de agg a la columna


Precio de cada uno de los grupos.

totales_descuento = pedidos.groupby(level = 0, axis = 0).Precio.agg(total_desc)


totales_descuento

101 3.0
102 2.0
103 7.0
Name: Precio, dtype: float64

La función agg admite una lista de funciones de agregación como argumento. Como resultado
obtendremos un objeto de tipo DataFrame, con tantas columnas como funciones de agregación en la
lista.
resumen = pedidos.groupby(level = 0, axis = 0).Precio.agg([sum, total_desc])
resumen

sum total_desc

101 4.0 3.0

102 3.0 2.0

103 9.0 7.0

Otras formas de agregación

A diferencia de las funciones de agregación vistas en la sección anterior, el método transform de los
Groupgy permite aplicar una función a cada uno de los grupos, devolviendo como resultado una serie
con tantos valores como el tamaño del grupo (no un valor único por grupo).

El dataframe tiempos recoge medicciones del tiempo (en segundos) que tardan en ejecutarse varios
procesos en sus distintas versiones.

datos = {'Proceso': ['A', 'B', 'A', 'A', 'B', 'A', 'B' ],


'Version' : ['0.0', '0.1', '1.1', '2.3', '2.1', '3.0', '3.1'],
'Segundos' : [20, 20, 30, 40, 50, 60, 70 ]}

tiempos = pd.DataFrame(datos)
tiempos

Proceso Version Segundos

0 A 0.0 20

1 B 0.1 20

2 A 1.1 30

3 A 2.3 40

4 B 2.1 50

5 A 3.0 60

6 B 3.1 70

Supongamos que queremos calcular la diferencia de tiempos de cada proceso con respecto al mejor
tiempo de dicho proceso. La función de usuario diferencia recibe una serie como argumento, calcula el
mínimo valor (método min) y devuelve la diferencia entre cada valor de la serie y el mínimo calculado. El
resultado es una serie.
def diferencia(x):
minimo = x.min()
return x - minimo

El método transform permite aplicar la función diferencia a cada uno de los grupos.

t = tiempos.groupby(['Proceso']).Segundos.transform(diferencia)
t

0 0
1 0
2 10
3 20
4 30
5 40
6 50
Name: Segundos, dtype: int64

tiempos.insert(3, 'Diferencia', t)
tiempos

Proceso Version Segundos Diferencia

0 A 0.0 20 0

1 B 0.1 20 0

2 A 1.1 30 10

3 A 2.3 40 20

4 B 2.1 50 30

5 A 3.0 60 40

6 B 3.1 70 50

Si queremos calcular la desviación de tiempos con respecto a la media de tiempos de cada proceso,
de nimos la función desviacion.

def desviacion(x):
media = x.mean()
return x - media

d = tiempos.groupby(['Proceso']).Segundos.transform( desviacion )
d

0 -17.500000
1 -26.666667
2 -7.500000
3 2.500000
4 3.333333
5 22.500000
6 23.333333
Name: Segundos, dtype: float64
tiempos.insert(4, 'Desv', d)
tiempos

Proceso Version Segundos Diferencia Desv

0 A 0.0 20 0 -17.500000

1 B 0.1 20 0 -26.666667

2 A 1.1 30 10 -7.500000

3 A 2.3 40 20 2.500000

4 B 2.1 50 30 3.333333

5 A 3.0 60 40 22.500000

6 B 3.1 70 50 23.333333

El método apply es más general que cualquiera de los métodos agg o transform. Por ejemplo,
podemos calcular la suma de los tiempos de cada proceso de distintas formas.

tiempos.groupby(['Proceso']).Segundos.sum()

Proceso
A 150
B 140
Name: Segundos, dtype: int64

tiempos.groupby(['Proceso']).Segundos.agg(np.sum)

Proceso
A 150
B 140
Name: Segundos, dtype: int64

tiempos.groupby(['Proceso']).Segundos.apply(np.sum)

Proceso
A 150
B 140
Name: Segundos, dtype: int64

En los ejemplos anteriores, el resultado es el mismo tanto si aplicamos la función sum directamente,
como si aplicamos np.sum mediante agg o apply. En el siguiente ejemplo, mostramos que existen
diferencias.

Creamos un nuevo dataframe a partir de tiempos seleccionando las columnas Proceso y Segundos.
sub = tiempos.loc[:,['Proceso', 'Segundos']]
sub

Proceso Segundos

0 A 20

1 B 20

2 A 30

3 A 40

4 B 50

5 A 60

6 B 70

A continuación agrupamos por proceso y aplicamos la función sum.

sub.groupby(['Proceso']).sum()

Segundos

Proceso

A 150

B 140

sub.groupby(['Proceso']).apply(np.sum)

Proceso Segundos

Proceso

A AAAA 150

B BBB 140

El método apply aplica la función np.sum a todas las columnas. La suma de los valores de la columna
Proceso es la concatenación de cadenas.
El siguiente ejemplo utiliza el método apply para obtener los dos procesos que emplean menos tiempo
en ejecutarse. Las funciones de agregación (tanto las prede nidas como las de nidas por el usuario)
transforman un grupo en un valor escalar. Pero en este caso, necesitamos aplicar una función que
transforme un grupo en un conjunto de las (dos por cada grupo, los dos procesos más rápidos).

De nimos la función mejores_tiempos.

def mejores_tiempos(datos):
ordenado = datos.sort_values(by = 'Segundos')
return ordenado[:2]

Aplicamos la función de usuario mejores_tiempos usando el método apply. El resultado es una serie
con índice jerárquico. El primer nivel de índice es el código del proceso, el el segundo nivel se
corresponde con el índice en el dataframe original.

sub.groupby('Proceso').apply(mejores_tiempos).Segundos

Proceso
A 0 20
2 30
B 1 20
4 50
Name: Segundos, dtype: int64

Referencias

Big Data. Análisis de datos con Python. Sarasa Cabezuelo, Antonio; García Ruiz, Yolanda Aditorial
Garceta. ISBN: 978-84-1622-883-6
Python Data Analysis Library (http://pandas.pydata.org/)
Python for Data Analysis (http://shop.oreilly.com/product/0636920023784.do)
Sección 6.4

Ejemplo

A continuación mostramos un ejemplo de uso de pandas mediante un ejercicio resuelto.

Datasets

Los siguientes datasets contienen datos históricos de combates de boxeo( 1993 - 2019 )

Combates: './data/ ght_data.csv'


Participantes: './data/ ghter_details.csv'

Descripción de los datasets

El dataset ./data/ ght_data.csv' contiene 5144 registros con información relacionada con combates de
boxeo. Cada registro hace referencia un combate, con información de los luchadores, tipo de combate,
resultado del combate, etc. A continuación describimos algunos de los campos más importantes:

R_fighter : nombre del luchador de la esquina roja


B_fighter : nombre del luchador de la esquina azul

Los pre jos R_ y B_ se re eren a los luchadores de las esquinas rojas y azules respectivamente.

Referee : nombre del árbitro del combate


date : fecha de celebración del combate
location : es el lugar en el que se produjo el evento
Format : es el formato de la pelea (3 asaltos, 5 asaltos etc.)
weight_class : categoría de la pelea (peso ligero, etc)
win_by : es un método de ganar (KO, etc.)
Winner : es el nombre del ganador del combate
last_round : es el último asalto del combate

Por otro lado, el dataset ./data/ ghter_details.csv contiene infomación de luchadores de boxeo, el
nombre, altura y el peso.
Ejercicio 1

Creación de dataframes:

Dataframe llamado combates con los datos del chero fight_data.csv.


Dataframe llamado luchadores con los datos del chero fighter_details.csv.

Antes de realizar la lectura de los cheros, revisamos el separador de campos de cada uno de ellos.
Utilizamos el parámetro parse_dates en la función read_csv para indicar al intérprete de Python que
el tipo de la columna date debe ser de tipo datetime.

Usamos las funciones necesarias para visualizar información general de cada uno de los dataframes
(Número de registros, nombres de las columnas, valores distintos de nulo para cada una de las columnas,
el tipo de datos de cada una de las columnas, etc).

# Sol:
import pandas as pd
combates = pd.read_csv('./data/fight_data.csv', delimiter =';', parse_dates = ['date'])
luchadores = pd.read_csv('./data/fighter_details.csv')

Visualizamos las prmeras 5 las:

combates.head()

R_ ghter B_ ghter win_by last_round last_round_time Format Referee date location Winner we

5 Rnd Chicago,
Henry Marlon Marc 2019- Henry
0 KO/TKO 3 4:51 (5-5-5- Illinois, Bant
Cejudo Moraes Goddard 06-08 Cejudo
5-5) USA

5 Rnd Chicago,
Valentina Jessica Robert 2019- Valentina Wom
1 KO/TKO 2 0:26 (5-5-5- Illinois,
Shevchenko Eye Madrigal 06-08 Shevchenko Flyw
5-5) USA

TKO - Chicago,
Tony Donald 3 Rnd Dan 2019- Tony
2 Doctor's 2 5:00 Illinois, Light
Ferguson Cerrone (5-5-5) Miragliotta 06-08 Ferguson
Stoppage USA

Chicago,
Jimmie Decision - 3 Rnd Kevin 2019-
3 Petr Yan 3 5:00 Illinois, Petr Yan Bant
Rivera Unanimous (5-5-5) MacDonald 06-08
USA

Chicago,
Blagoy Decision - 3 Rnd Dan 2019- Blagoy
4 Tai Tuivasa 3 5:00 Illinois, Heav
Ivanov Unanimous (5-5-5) Miragliotta 06-08 Ivanov
USA

Visualizamos las prmeras 5 las:


luchadores.head()

ghter_name Weight Height

0 AJ Fonseca 145 lbs. 5' 4"

1 AJ Matthews 185 lbs. 5' 11"

2 AJ McKee 145 lbs. 5' 10"

3 AJ Siscoe 135 lbs. 5' 7"

4 Aalon Cruz 145 lbs. 6' 0"

combates.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5144 entries, 0 to 5143
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 R_fighter 5144 non-null object
1 B_fighter 5144 non-null object
2 win_by 5144 non-null object
3 last_round 5144 non-null int64
4 last_round_time 5144 non-null object
5 Format 5144 non-null object
6 Referee 5121 non-null object
7 date 5144 non-null datetime64[ns]
8 location 5144 non-null object
9 Winner 5061 non-null object
10 weight_class 5144 non-null object
11 title_bout 5144 non-null bool
dtypes: bool(1), datetime64[ns](1), int64(1), object(9)
memory usage: 447.2+ KB

luchadores.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3313 entries, 0 to 3312
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 fighter_name 3313 non-null object
1 Weight 3238 non-null object
2 Height 3050 non-null object
dtypes: object(3)
memory usage: 77.8+ KB

Ejercicio 2

En el dataframe luchadores se encuentran los datos asociados a los participantes en combates. En


particular recoje el peso de los participantes expresado en libras.

a) ¿Cuántos valores NaN hay en la columna Weight?

b) Elimina de nitivamente los valores NaN del dataframe luchadores.


# Sol a)
print(luchadores.Weight.isna().sum())

75

Para eliminar de nitivamente los valores NaN, usamos la operación dropna con el parámetro inplace
con valor true. Así el dataframe tendrá solo valores distintos de NaN.

# Sol b)
luchadores.dropna( inplace = True)
luchadores.head()

ghter_name Weight Height

0 AJ Fonseca 145 lbs. 5' 4"

1 AJ Matthews 185 lbs. 5' 11"

2 AJ McKee 145 lbs. 5' 10"

3 AJ Siscoe 135 lbs. 5' 7"

4 Aalon Cruz 145 lbs. 6' 0"

luchadores.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 3044 entries, 0 to 3312
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 fighter_name 3044 non-null object
1 Weight 3044 non-null object
2 Height 3044 non-null object
dtypes: object(3)
memory usage: 95.1+ KB

Como se puede observar, el dataframe luchadores tiene ahora 3044 las y ningún valor NaN.

Ejercicio 3

Modi ca el DataFrame luchadores. Crea una nueva columna llamada Weight gr que represente el
peso del luchador en gramos. (1 libra son 453.59 gramos). Observa que antes de crear la columna, has de
eliminar las unidades (cuatro últimos caracteres de cada valor en la columna Weight, para así quedarte
con un string que representa un número. Una vez eliminada las unidades, convierte dicho string a tipo
float y posteriormente transforma a gramos.
# Sol
peso = luchadores.Weight.map(lambda x: float(x[:-4]))
luchadores['Weight gr' ]= peso * 453.59
luchadores.head()

ghter_name Weight Height Weight gr

0 AJ Fonseca 145 lbs. 5' 4" 65770.55

1 AJ Matthews 185 lbs. 5' 11" 83914.15

2 AJ McKee 145 lbs. 5' 10" 65770.55

3 AJ Siscoe 135 lbs. 5' 7" 61234.65

4 Aalon Cruz 145 lbs. 6' 0" 65770.55

Ejercicio 4

La columna location del dataframe combates recoge información del lugar donde se celebran los
combates. Este campo recoge la ciudad, el estado y el país.

Modi ca el DataFrame combates. Crear una nueva columna denominada Country con información
únicamente del nombre del país. Te servirá de inspiración observar el comportamiento del siguiente
fragmento de código:

texto = 'Chicago, Illinois, USA'


parts = texto.split(',')
parts

['Chicago', ' Illinois', ' USA']

Como puedes observar, el país siempre es el último elemento de la lista parts (posición -1 de la lista).
Para resolver este apartado, deberás crear una función que extraiga el país y aplicar dicha función a toda
la columna location mediante map.
# Sol a)
combates['Country'] = combates.location.map(lambda x : x.split(',')[-1])
combates.head()

R_ ghter B_ ghter win_by last_round last_round_time Format Referee date location Winner we

5 Rnd June Chicago,


Henry Marlon Marc Henry
0 KO/TKO 3 4:51 (5-5-5- 08, Illinois, Banta
Cejudo Moraes Goddard Cejudo
5-5) 2019 USA

5 Rnd June Chicago,


Valentina Jessica Robert Valentina Wom
1 KO/TKO 2 0:26 (5-5-5- 08, Illinois,
Shevchenko Eye Madrigal Shevchenko Flywe
5-5) 2019 USA

TKO - June Chicago,


Tony Donald 3 Rnd Dan Tony
2 Doctor's 2 5:00 08, Illinois, Lightw
Ferguson Cerrone (5-5-5) Miragliotta Ferguson
Stoppage 2019 USA

June Chicago,
Jimmie Decision - 3 Rnd Kevin
3 Petr Yan 3 5:00 08, Illinois, Petr Yan Banta
Rivera Unanimous (5-5-5) MacDonald
2019 USA

June Chicago,
Blagoy Decision - 3 Rnd Dan Blagoy
4 Tai Tuivasa 3 5:00 08, Illinois, Heavy
Ivanov Unanimous (5-5-5) Miragliotta Ivanov
2019 USA

Ejercicio 5

Muestra el número de combates por árbitro y categoría. Usa groupby.

combates.groupby(['Referee', "weight_class"]).date.count()

Referee weight_class
Adam Cheadle Bantamweight 1
Flyweight 1
Lightweight 2
Adam Martinez Bantamweight 1
Featherweight 1
..
Yves Lavigne Welterweight 52
Women's Bantamweight 7
Women's Featherweight 1
Women's Flyweight 1
Women's Strawweight 4
Name: date, Length: 854, dtype: int64
Ejercicio 6

Muestra los datos de los combates ordenados por fecha en orden descendente.
# Sol:
ordenados = combates.sort_values("date", ascending = False)
ordenados.head(10)

R_ ghter B_ ghter win_by last_round last_round_time Format Referee date location Winner

5 Rnd Chicago,
Henry Marlon Marc 2019- Henry
0 KO/TKO 3 4:51 (5-5-5- Illinois,
Cejudo Moraes Goddard 06-08 Cejudo
5-5) USA

Chicago,
Karolina Alexa Decision - 3 Rnd Kevin 2019- Alexa
7 3 5:00 Illinois,
Kowalkiewicz Grasso Unanimous (5-5-5) MacDonald 06-08 Grasso
USA

5 Rnd Chicago,
Valentina Robert 2019- Valentina
1 Jessica Eye KO/TKO 2 0:26 (5-5-5- Illinois,
Shevchenko Madrigal 06-08 Shevchenko
5-5) USA

Chicago,
Katlyn Joanne Decision - 3 Rnd Dan 2019- Katlyn
12 3 5:00 Illinois,
Chookagian Calderwood Unanimous (5-5-5) Miragliotta 06-08 Chookagian
USA

Chicago,
Eddie Grigorii 3 Rnd Kevin 2019- Eddie
11 KO/TKO 2 4:47 Illinois,
Wineland Popov (5-5-5) MacDonald 06-08 Wineland
USA

Chicago,
Darren Decision - 3 Rnd Marc 2019- Darren
10 Bevon Lewis 3 5:00 Illinois,
Stewart Unanimous (5-5-5) Goddard 06-08 Stewart
USA

Chicago,
Ricardo Calvin 3 Rnd Dan 2019- Calvin
8 KO/TKO 1 4:06 Illinois,
Lamas Kattar (5-5-5) Miragliotta 06-08 Kattar
USA

Chicago,
Decision - 3 Rnd Robert 2019- Yan
9 Yan Xiaonan Angela Hill 3 5:00 Illinois,
Unanimous (5-5-5) Madrigal 06-08 Xiaonan
USA

Chicago,
Aljamain Pedro Decision - 3 Rnd Marc 2019- Aljamain
6 3 5:00 Illinois,
Sterling Munhoz Unanimous (5-5-5) Goddard 06-08 Sterling
USA

Chicago,
Tatiana Nina Decision - 3 Rnd Robert 2019- Tatiana
5 3 5:00 Illinois,
Suarez Ansaro Unanimous (5-5-5) Madrigal 06-08 Suarez
USA
Ejercicio 7

Muestra un listado de paises distintos donde se han celebrado combates. Usa unique.

# Sol b) Muestra un listado de paises distintos donde se han celebrado combates.


paises = combates.Country.unique()
paises

array([' USA', ' Sweden', ' Brazil', ' Canada', ' Russia',
' United Kingdom', ' Czech Republic', ' Australia', ' China',
' Argentina', ' Germany', ' Singapore', ' Chile', ' Poland',
' Japan', ' Netherlands', ' Mexico', ' New Zealand', ' Croatia',
' South Korea', ' Ireland', ' Philippines',
' United Arab Emirates', ' Puerto Rico'], dtype=object)

Ejercicio 8

Deseamos conocer en cuántos combates ha sido victorioso el luchador de calzoncillo rojo. Observa que la
columna Winner informa del nombre del luchador que ha ganado el combate.

combates.head(2)

R_ ghter B_ ghter win_by last_round last_round_time Format Referee date location Winner weight_cla

5 Rnd June Chicago,


Henry Marlon Marc Henry
0 KO/TKO 3 4:51 (5-5-5- 08, Illinois, Bantamweig
Cejudo Moraes Goddard Cejudo
5-5) 2019 USA

5 Rnd June Chicago,


Valentina Jessica Robert Valentina Women's
1 KO/TKO 2 0:26 (5-5-5- 08, Illinois,
Shevchenko Eye Madrigal Shevchenko Flyweight
5-5) 2019 USA

Creamos un ltro con la condición de que el nombre del ganador sea el mismo que el nombre del
luchador del calzoncillo rojo. Posteriormente, aplicamos el ltro al dataframe usando [ ]:

# Sol:
filtro = combates.R_fighter == combates.Winner
ganador_rojo = combates[filtro]
print('Ganador rojo en: ', ganador_rojo.shape[0], ' combates')

Ganador rojo en: 3470 combates

Ejercicio 9
a) Genera un listado de combates que se hayan ganado por KO/TKO. La columna win_by informa cómo
se ha ganado el combate.

Creamos un ltro con la condición de que el valor de la columna win_by sea KO/TKO. Posteriormente,
aplicamos el ltro al dataframe usando [ ]:
# Sol a)
filtro = combates.win_by == 'KO/TKO'
ganador_por_ko = combates[filtro]
ganador_por_ko

R_ ghter B_ ghter win_by last_round last_round_time Format Referee date location Winner

5 Rnd Chicago,
Henry Marlon Marc 2019- Henry
0 KO/TKO 3 4:51 (5-5-5- Illinois,
Cejudo Moraes Goddard 06-08 Cejudo
5-5) USA

5 Rnd Chicago,
Valentina Robert 2019- Valentina
1 Jessica Eye KO/TKO 2 0:26 (5-5-5- Illinois,
Shevchenko Madrigal 06-08 Shevchenko
5-5) USA

Chicago,
Ricardo Calvin 3 Rnd Dan 2019- Calvin
8 KO/TKO 1 4:06 Illinois,
Lamas Kattar (5-5-5) Miragliotta 06-08 Kattar
USA

Chicago,
Eddie Grigorii 3 Rnd Kevin 2019- Eddie
11 KO/TKO 2 4:47 Illinois,
Wineland Popov (5-5-5) MacDonald 06-08 Wineland
USA

Jimi Aleksandar 3 Rnd Kevin 2019- Stockholm, Aleksandar


14 KO/TKO 1 0:47
Manuwa Rakic (5-5-5) Sataki 06-01 Sweden Rakic

... ... ... ... ... ... ... ... ... ... ...

No Denver,
Orlando Robert John 1994- Orlando
5131 KO/TKO 1 2:50 Time Colorado,
Wiet Lucarelli McCarthy 03-11 Wiet
Limit USA

No Denver,
Johnny David John 1994- Johnny
5133 KO/TKO 1 12:13 Time Colorado,
Rhodes Levicki McCarthy 03-11 Rhodes
Limit USA

No Joao Denver,
Gerard Kevin 1993- Gerard
5139 KO/TKO 1 0:59 Time Alberto Colorado,
Gordeau Rosier 11-12 Gordeau
Limit Barreto USA

No Joao Denver,
Kevin Zane 1993- Kevin
5142 KO/TKO 1 4:20 Time Alberto Colorado,
Rosier Frazier 11-12 Rosier
Limit Barreto USA

No Joao Denver,
Gerard 1993- Gerard
5143 Teila Tuli KO/TKO 1 0:26 Time Alberto Colorado,
Gordeau 11-12 Gordeau
Limit Barreto USA
1647 rows × 12 columns

Ejercicio 10

Muestra un listado de los combates arbitrados por Robert Madrigal que han sigo ganados por
KO/TKO. En este caso creamos dos ltros y los aplicamos a la tabla mediante el operador conjunción (&):
filtro1 = combates.win_by == 'KO/TKO'
filtro2 = combates.Referee == 'Robert Madrigal'
ganador_por_ko_robert = combates[filtro1 & filtro2]
ganador_por_ko_robert

R_ ghter B_ ghter win_by last_round last_round_time Format Referee date location Winner weig

5 Rnd Chicago,
Valentina Jessica Robert 2019- Valentina Wome
1 KO/TKO 2 0:26 (5-5-5- Illinois,
Shevchenko Eye Madrigal 06-08 Shevchenko Flyweig
5-5) USA

Chicago,
Frankie Eddie 3 Rnd Robert 2016- Eddie
1387 KO/TKO 3 1:54 Illinois, Bantam
Saenz Wineland (5-5-5) Madrigal 07-23 Wineland
USA

Chicago,
Gian Tom 3 Rnd Robert 2015- Light
1823 KO/TKO 2 0:27 Illinois, Tom Lawlor
Villante Lawlor (5-5-5) Madrigal 07-25 Heavyw
USA

Chicago,
Eddie Yves 3 Rnd Robert 2014- Eddie
2589 KO/TKO 2 4:16 Illinois, Bantam
Wineland Jabouin (5-5-5) Madrigal 01-25 Wineland
USA

Chicago,
Nikita 3 Rnd Robert 2014- Nikita
2594 Walt Harris KO/TKO 1 0:25 Illinois, Heavyw
Krylov (5-5-5) Madrigal 01-25 Krylov
USA

Chicago,
Anthony Donald 3 Rnd Robert 2013- Anthony
2983 KO/TKO 1 2:35 Illinois, Lightw
Pettis Cerrone (5-5-5) Madrigal 01-26 Pettis
USA

Chicago,
Lavar 3 Rnd Robert 2012- Lavar
3322 Joey Beltran KO/TKO 1 4:24 Illinois, Heavyw
Johnson (5-5-5) Madrigal 01-28 Johnson
USA

Ejercicio 11

Calcula el número de combates por categoría. Puedes usar value_counts.


# Sol a) Calcula el número de combates por categoría.
combates_by_cat = combates['weight_class'].value_counts()
combates_by_cat

Lightweight 989
Welterweight 969
Middleweight 725
Heavyweight 507
Light Heavyweight 502
Featherweight 442
Bantamweight 379
Flyweight 187
Women's Strawweight 143
Women's Bantamweight 111
Open Weight 92
Women's Flyweight 50
Catch Weight 38
Women's Featherweight 10
Name: weight_class, dtype: int64

Ejercicio 12

Nos interesa construir un dataframe que reuna la información de los combates y el peso del ganador.
Usa la operación merge.
# Sol:

resume = pd.merge(combates, luchadores.loc[:,['fighter_name', 'Weight']],


right_on = 'fighter_name', left_on = 'Winner').head()
resume

R_ ghter B_ ghter win_by last_round last_round_time Format Referee date location Winner weig

5 Rnd Chicago,
Henry Marlon Marc 2019- Henry
0 KO/TKO 3 4:51 (5-5-5- Illinois, Banta
Cejudo Moraes Goddard 06-08 Cejudo
5-5) USA

5 Rnd Brooklyn,
Henry TJ Kevin 2019- Henry
1 KO/TKO 1 0:32 (5-5-5- New York, Flywei
Cejudo Dillashaw MacDonald 01-19 Cejudo
5-5) USA

Los
5 Rnd
Demetrious Henry Decision - Mike 2018- Angeles, Henry
2 5 5:00 (5-5-5- Flywei
Johnson Cejudo Split Beltran 08-04 California, Cejudo
5-5)
USA

Detroit,
Henry Sergio Decision - 3 Rnd Keith 2017- Henry
3 3 5:00 Michigan, Flywei
Cejudo Pettis Unanimous (5-5-5) Peterson 12-02 Cejudo
USA

Edmonton,
Henry Wilson 3 Rnd 2017- Henry
4 KO/TKO 2 0:25 Jerin Valel Alberta, Flywei
Cejudo Reis (5-5-5) 09-09 Cejudo
Canada
Sección 6.5

Ejercicios

Pregunta 1

Crear dataframes con la información de los siguientes cheros.

El contenido y el formato de los cheros no se puede modi car.

Dataframe operations : details.xlsx Contiene información acerca de préstamos bancarios


concedidos a sus clientes.
loan_id: identi cador del préstamo
client: código del cliente
loan_amnt: importe del préstamo
term: plazo del préstamo
int_rate : tasa de interés
installment: gastos de gestión del préstamo
loan_status: estado en el que se encuentra el préstamo
purpose: nalidad del préstamo
date_of_application: fecha de solicitud
last_pymnt_amnt: importe del último recibo
application_type: tipo de solicitud

Dataframe clientes : clients.csv Contiene información de los clientes. Observa que la


extensión del chero es csv.
Client_name: código del cliente
grade: nivel de estudios
emp_title: profesión del clietne
emp_length: antigüedad en la empresa
home_ownership: indicador de si el cliente tiene casa en propiedad
annual_inc: ingresos brutos anuales
addr_state: estado en el que reside el cliente

# Sol:
Pregunta 2.

La columna loan_status recoge el estado del préstamo bancario. Muestra los distintos estados
disponibles. Puedes usar la operación unique().

# SOl:

Pregunta 3.

La columna loan_amnt recoge el importe del préstamo. ¿Cuántos préstamos tienen un importe superior
a 25000?

# SOL:

Pregunta 4.

El tipo de interés asociado a los préstamos se recoge en la columna int_rate. Crear un nuevo
dataframe con la información de los préstamos cuyo tipo de interés sea inferior al 10%. Muestra las 10
primeras las del dataframe recién creado.

# SOL:

Pregunta 5.

La columna term indica el plazo del préstamo y la columna loan_amnt recoge el importe del préstamo.
Calcula el importe medio de los préstamos a 36 meses cuya nalidad es home. La nalidad del préstamo
se recoge en la columna purpose.

# SOL:

Pregunta 6.

La columna loan_amnt recoge el importe del préstamo, mientras que el tipo de interés asociado a los
préstamos se recoge en la columna int_rate. Ordenar los datos de los préstamos en orden creciente de
tipo de interés y decreciente de importe del préstamo.

# SOL:

Pregunta 7.
La columna home_ownership recoge información del tipo de propiedad del cliente. Modi ca el valor de
dicha columna para que recoja el valor Bajo Riesgo en caso de RENT, y el valor Alto Riesgo en caso
de MORTGAGE.

# SOL:

Pregunta 8.

Calcular el máximo salario de los clientes por grado de estudios y estado de residencia. La columna
grade indica el grado de estudios del solicitante del préstamo y la columna addr_state recoge
información del estado de residencia del cliente.

# SOL:

Pregunta 9.

La columna emp_title recoge información del puesto de trabajo de cliente. Crea un nuevo dataframe
con los datos de los clientes, donde los valores NaN en dicha columna se sustituyan por el valor Rest.
Guarda el nuevo dataframe en un chero csv utilizando el símbolo ; como separador de campos.

# SOL:

Pregunta 10.

Calcula el número de clientes con nivel de estudios A que han pedido un préstamo cuya nalidad es
vacation.

# SOL:

También podría gustarte