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

In [2]:
import numpy as np
import pandas as pd

import matplotlib as mpl
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.

In [3]:
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
Out[3]:
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 la clase 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.

In [4]:
pedidos.pivot(index = 'Número_pedido', columns = 'Producto', values = 'Cantidad')
Out[4]:
Producto p1 p2 p3 p4
Número_pedido
101 2 3 4 None
102 None 2 1 5
103 3 1 None 2

El método pivot exige que no existan dos filas 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 filas 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 la clase DataFrame. Su funcionamiento es similar a pivot con la ventaja de que permite aplicar funciones de agregación.

In [5]:
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]]
In [6]:
prod = pd.DataFrame(datos, columns = ['Ejercicio', 'Trimestre', 'Producto', 'Kg.'])
prod
Out[6]:
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
In [7]:
prod.pivot_table(index = 'Ejercicio', columns = 'Producto', values = 'Kg.', aggfunc = sum)
Out[7]:
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.

In [8]:
prod.pivot_table(index = 'Ejercicio', columns = 'Producto', values = 'Kg.',
                 aggfunc = sum, fill_value = 0)
Out[8]:
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 la clase DataFrame permite identificar filas repetidas. Como resultado se obtiene una serie cuyos valores son de tipo bool, cada uno de los cuales está asociado a una fila. El valor True indica que ya existe una fila 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 filas repetidas:

In [9]:
t = pd.DataFrame([['Africa', 11506],
       ['Africa', 11506],
       ['Antarctica', 5500],
       ['Antarctica', 5500],
       ['Africa', 11506],
       ['Banks', 23], 
       ['Africa', 10000]], columns = ['Nombre', 'Millas cuadradas'])
t
Out[9]:
Nombre Millas cuadradas
0 Africa 11506
1 Africa 11506
2 Antarctica 5500
3 Antarctica 5500
4 Africa 11506
5 Banks 23
6 Africa 10000
In [10]:
t.duplicated()
Out[10]:
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 filtro para obtener un dataframe con las filas repetidas.

In [11]:
t[t.duplicated()]
Out[11]:
Nombre Millas cuadradas
1 Africa 11506
3 Antarctica 5500
4 Africa 11506

Para eliminar las filas repetidas de un dataframe utilizamos el método drop_duplicates de la clase DataFrame.

In [12]:
t.drop_duplicates()
Out[12]:
Nombre Millas cuadradas
0 Africa 11506
2 Antarctica 5500
5 Banks 23
6 Africa 10000

También nos puede interesar eliminar filas si se repiten los valores de un subconjunto de columnas. En ese caso es necesario indicar dicho subconjunto de columnas en el argumento subset.

In [14]:
t.drop_duplicates(subset = ['Nombre'])
Out[14]:
Nombre Millas cuadradas
0 Africa 11506
2 Antarctica 5500
5 Banks 23

Por defecto se mantiene la primera fila y se elimina el resto de filas repetidas. El argumento keep con valor last permite eliminar todas las filas repetidas excepto la última aparición.

In [15]:
t.drop_duplicates(subset = ['Nombre'], keep = 'last')
Out[15]:
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 definidas 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.

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

In [17]:
np.sqrt(t)
Out[17]:
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.

In [18]:
np.square(t)
Out[18]:
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 definidas en Numpy, si no que también es posible aplicar funciones definidas por el usuario. En el caso de objetos de tipo Series es necesario utilizar el método map para aplicar funciones de usuario.

In [19]:
f1 = lambda x: x ** 2 - 2
In [20]:
s = pd.Series([2, 4, 6, 8 ], index = ['EE', 'IR', 'GR', 'ES'])
s
Out[20]:
EE    2
IR    4
GR    6
ES    8
dtype: int64
In [21]:
s.map(f1)
Out[21]:
EE     2
IR    14
GR    34
ES    62
dtype: int64

En el caso de objetos de tipo DataFrame usaremos el método applymap.

In [22]:
t.applymap(f1)
Out[22]:
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.

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

In [24]:
t['Nueva'] = t['2004'].map(f2)
t
Out[24]:
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 filas o columnas

El método apply de la clase DataFrame permite aplicar funciones de usuario tanto a filas como a columnas de un dataframe.

In [25]:
f3 = lambda x: x.sum() - x.min()
In [26]:
t = pd.DataFrame( [[10, 20, 30], 
                   [40, 10, 60],
                   [70,  80, 10]],
            index  = ['P1', 'P2', 'P3'],
                  columns = ['A', 'B', 'C'])
t
Out[26]:
A B C
P1 10 20 30
P2 40 10 60
P3 70 80 10
In [27]:
t.apply(f3)
Out[27]:
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 filas.

In [28]:
t.apply(f3, axis = 1)
Out[28]:
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.

In [29]:
def f4(x):
    return pd.Series([sum(x) , min(x), f3(x) ], index = ['Suma', 'Mínimo', 'f3'])
In [30]:
t.apply(f4, axis = 1)
Out[30]:
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