El post introduce la librería Pandas para la construcción y manipulación de estructuras de datos basados en Series y DataFrame.
Disclaimer A: La información contenida en esta página está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional y fue construida bajo mi rol como ayudante (Teacher Assistant) de la Catedra Business Intelligence para las Finanzas.
Disclaimer B: Este post está basado en mis propios resumenes a partir de los capítulos del libro Python for Data Analysis: Data Wrangling with Pandas, NumPy, and IPython, de Wes McKinney. La mayoría de los ejemplos provienen de momento del libro.
Advertencia: La página es un complemento a la cátedra y por nada la sustituye.
Pandas adopta mucha “terminología” desde NumPy, pero la diferencia principal es que está diseñado para trabajar con datos tabulados o heterogeneos. NumPy, por el contrario, trabaja con datos homogeneos, números en arrays.
Por convención se utiliza:
import pandas as pd
Cuando veamos pd. en el código, se estará haciendo referencia a pandas. Es recomendable importar Series y DataFrame al workspace local, debido que son usados con frecuencia:
from pandas import Series, DataFrame
En pandas existen dos estructura de datos principales:
Series: es un array de una dimensión (one-dimensional array) con una secuancia de valores y asociado una etiqueta (label) denominada index.
DataFrame: representa datos rectangulares que contiene colecciones de columnas que puede contener valores del tipo, numerico (numeric), string, boolean, etc. Contiene dos index, fila y columna. Se puede ver como un diccionarios de Series que comparten un index en común.
Si queremos crear la secuencia de números del 1 al 8:
# también se podría haber construido con range(8)
obj_series = pd.Series([1, 2, 3, 4, 5, 6, 7])
obj_series
0 1
1 2
2 3
3 4
4 5
5 6
6 7
dtype: int64
Del output vemos que al ser del tipo Series, nos muestra el tipo de datos que contiene, en este caso, dtype: int64, es decir, integers de 64 bits.
Si queremos ver sus componentes, valores e índices:
# valores
obj_series.values
array([1, 2, 3, 4, 5, 6, 7])
# index, como range(8)
obj_series.index
RangeIndex(start=0, stop=7, step=1)
Tener en consideración que al no especificar un index a nuestros datos, por default nos mostrará del 0 hasta n-1. Para un index en especifico se usa:
obj_series_2 = pd.Series([1, 2, 3, 4, 5, 6, 7], index = ['a', 'b', 'c', 'd', 'e', 'f', 'g'])
obj_series_2.index
Index(['a', 'b', 'c', 'd', 'e', 'f', 'g'], dtype='object')
Comparado con los arrays de Numpy, se puede usar las etiquetas del index:
obj_series_2['a']
1
obj_series_2['h'] = 8 # se agrega un nuevo elemento a la Series con el index h
obj_series_2[['a', 'b', 'c']]
a 1
b 2
c 3
dtype: int64
obj_series_2[['a', 'd', 'e', 'f', 'h']]
a 1
d 4
e 5
f 6
h 8
dtype: int64
A partir de un diccionario podemos crear una Series:
comunas_stgo = {'Santiago': 50000, 'Providencia': 65000, 'Huechuraba': 32000, 'Quilicura': 76000}
obj_series_3 = pd.Series(comunas_stgo)
obj_series_3
Santiago 50000
Providencia 65000
Huechuraba 32000
Quilicura 76000
dtype: int64
Cada llave del diccionario será ahora el index de la Series.
nuevas_comunas = ['Santiago', 'Providencia', 'Huechuraba', 'San Miguel']
obj_series_4 = pd.Series(comunas_stgo, index = nuevas_comunas)
obj_series_4
Santiago 50000.0
Providencia 65000.0
Huechuraba 32000.0
San Miguel NaN
dtype: float64
El objeto obj_series_4 contiene 4 cuatro valores, pero como no existía el index San Miguel, Python agrega un NaN (not a number), que es como considera los valores missing (NA). Como Quilicura no estaba incluido en nuevas_comunas, es excluido. Para detectar los missing, se puede usar isnull o notnull.
pd.isnull(obj_series_4)
Santiago False
Providencia False
Huechuraba False
San Miguel True
dtype: bool
pd.notnull(obj_series_4)
Santiago True
Providencia True
Huechuraba True
San Miguel False
dtype: bool
También se puede sumar los DataFrames:
obj_series_3 + obj_series_4
Huechuraba 64000.0
Providencia 130000.0
Quilicura NaN
San Miguel NaN
Santiago 100000.0
dtype: float64
O aplicar operadores matemáticos:
obj_series_4 * 2
Santiago 100000.0
Providencia 130000.0
Huechuraba 64000.0
San Miguel NaN
dtype: float64
obj_series_4 + 10000
Santiago 60000.0
Providencia 75000.0
Huechuraba 42000.0
San Miguel NaN
dtype: float64
obj_series_4 - 10000
Santiago 40000.0
Providencia 55000.0
Huechuraba 22000.0
San Miguel NaN
dtype: float64
obj_series_4[obj_series_4 > 50000]
Providencia 65000.0
dtype: float64
Existen muchas maneras de construir un DataFrame, siguiendo el ejemplo del libro Python for Data Analysis de Wez Mckinney:
data = {'estado': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'año': [2000, 2001, 2002, 2001, 2002, 2003],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)
frame
estado año pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
5 Nevada 2003 3.2
Para “llamar” una variable, se puede obtener utilizando la lógica de Series o por atributo:
frame['estado'] # como Series
0 Ohio
1 Ohio
2 Ohio
3 Nevada
4 Nevada
5 Nevada
Name: estado, dtype: object
frame.estado # como atributo
0 Ohio
1 Ohio
2 Ohio
3 Nevada
4 Nevada
5 Nevada
Name: estado, dtype: object
Utilizar “como Series” funciona para cualquier nombre de la columna, pero “como atributo” funciona cuando el nombre de la columna tiene el atributo name.
Si se quiere construir una nueva variable:
frame['deuda'] = 20.0
frame
estado año pop deuda
0 Ohio 2000 1.5 20.0
1 Ohio 2001 1.7 20.0
2 Ohio 2002 3.6 20.0
3 Nevada 2001 2.4 20.0
4 Nevada 2002 2.9 20.0
5 Nevada 2003 3.2 20.0
frame['deuda'] = np.arange(6.0)
Para eliminar una columna:
del frame['deuda']
frame.columns
Index(['estado', 'año', 'pop'], dtype='object')
Pandas posee un método denominado reindex, que permite reordenar el index de un objeto de pandas.
obj_para_reindex = pd.Series(range(4), index = ['d', 'c', 'b', 'a'])
obj_para_reindex
d 0
c 1
b 2
a 3
dtype: int64
obj_para_reindex.reindex(['a', 'b', 'c', 'd'])
a 3
b 2
c 1
d 0
dtype: int64
Si se agrega un index más, la Series tendrá un NaN. Para realizar una interpolación o filling
obj_fill = pd.Series(['azul', 'morado', 'amarillo'], index = [0, 2, 5])
obj_fill
0 azul
2 morado
5 amarillo
dtype: object
obj_fill.reindex(range(7), method='ffill')
0 azul
1 azul
2 morado
3 morado
4 morado
5 amarillo
6 amarillo
dtype: object
En un DataFrame, reindex puede alterar el index de filas, columnas o ambas.
frame_para_reindex = pd.DataFrame(np.arange(9).reshape((3, 3)),
index=['a', 'c', 'd'],
columns=['ciudad_a', 'ciudad_c', 'ciudad_d'])
frame_para_reindex
ciudad_a ciudad_c ciudad_d
a 0 1 2
c 3 4 5
d 6 7 8
frame_para_reindex.reindex(['a', 'b' , 'c', 'd'])
ciudad_a ciudad_c ciudad_d
a 0.0 1.0 2.0
b NaN NaN NaN
c 3.0 4.0 5.0
d 6.0 7.0 8.0
ciudades = ['ciudad_a', 'ciudad_b', 'ciudad_c', 'ciudad_d']
frame_para_reindex.reindex(columns=ciudades)
ciudad_a ciudad_b ciudad_c ciudad_d
a 0 NaN 1 2
c 3 NaN 4 5
d 6 NaN 7 8
En Series:
obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
obj.drop('c')
a 0.0
b 1.0
d 3.0
e 4.0
dtype: float64
obj.drop(['c', 'd'])
a 0.0
b 1.0
e 4.0
dtype: float64
En un DataFrame:
frame_DF = pd.DataFrame(np.arange(9).reshape((3, 3)),
index=['a', 'c', 'd'],
columns=['ciudad_a', 'ciudad_c', 'ciudad_d'])
frame_DF.drop('a', axis=0) # por default es axis=0, por fila
ciudad_a ciudad_c ciudad_d
c 3 4 5
d 6 7 8
frame_DF.drop(['a', 'c'], axis=0)
ciudad_a ciudad_c ciudad_d
d 6 7 8
frame_DF.drop('ciudad_c', axis=1) # también funciona axis='columns'
ciudad_a ciudad_d
a 0 2
c 3 5
d 6 8
frame_DF.drop(['ciudad_c', 'ciudad_d'], axis=1)
ciudad_a
a 0
c 3
d 6
En Series:
obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
obj
a 0.0
b 1.0
c 2.0
d 3.0
e 4.0
dtype: float64
obj['a']
0.0
obj[0]
0.0
obj[['a', 'b', 'c']]
a 0.0
b 1.0
c 2.0
dtype: float64
obj[0:3]
a 0.0
b 1.0
c 2.0
dtype: float64
obj[[0,1,2]]
a 0.0
b 1.0
c 2.0
dtype: float64
obj[obj < 2]
a 0.0
b 1.0
dtype: float64
obj['a':'c']
a 0.0
b 1.0
c 2.0
dtype: float64
En un DataFrame:
frame_DF = pd.DataFrame(np.arange(9).reshape((3, 3)),
index=['a', 'c', 'd'],
columns=['ciudad_a', 'ciudad_c', 'ciudad_d'])
frame_DF
ciudad_a ciudad_c ciudad_d
a 0 1 2
c 3 4 5
d 6 7 8
frame_DF['ciudad_a']
a 0
c 3
d 6
Name: ciudad_a, dtype: int64
frame_DF[['ciudad_d','ciudad_a']]
ciudad_d ciudad_a
a 2 0
c 5 3
d 8 6
frame_DF[:2]
ciudad_a ciudad_c ciudad_d
a 0 1 2
c 3 4 5
frame_DF[frame_DF['ciudad_c']>2]
ciudad_a ciudad_c ciudad_d
c 3 4 5
d 6 7 8
frame_DF > 4
ciudad_a ciudad_c ciudad_d
a False False False
c False False True
d True True True
frame_DF[frame_DF > 4] = 0
loc e ilocPandas posee un operador especial, loc (label/etiqueta) e iloc(integers).
frame_DF.loc['a', ['ciudad_a', 'ciudad_c']]
ciudad_a 0
ciudad_c 1
Name: a, dtype: int64
frame_DF.iloc[0, [0, 1]]
ciudad_a 0
ciudad_c 1
Name: a, dtype: int64
frame_DF.iloc[2]
ciudad_a 0
ciudad_c 0
ciudad_d 0
Name: d, dtype: int64
frame_DF.iloc[[0, 1], [2, 0, 1]]
ciudad_d ciudad_a ciudad_c
a 2 0 1
c 0 3 4
frame_DF.iloc[:, :2][frame_DF.ciudad_a > 2]
ciudad_a ciudad_c
c 3 4
NAsSe define dos objetos:
df_a = pd.DataFrame(np.arange(12.).reshape(3, 4), columns=list('abcd'))
df_a
a b c d
0 0.0 1.0 2.0 3.0
1 4.0 5.0 6.0 7.0
2 8.0 9.0 10.0 11.0
df_b = pd.DataFrame(np.arange(20.).reshape(4, 5), columns=list('abcde'))
df_b
a b c d e
0 0.0 1.0 2.0 3.0 4.0
1 5.0 6.0 7.0 8.0 9.0
2 10.0 11.0 12.0 13.0 14.0
3 15.0 16.0 17.0 18.0 19.0
Generamos una “celda” con nan
df_b.loc[1,'b'] = np.nan
df_b
a b c d e
0 0.0 1.0 2.0 3.0 4.0
1 5.0 NaN 7.0 8.0 9.0
2 10.0 11.0 12.0 13.0 14.0
3 15.0 16.0 17.0 18.0 19.0
Si sumamos el objeto df_a y df_b:
df_a + df_b
a b c d e
0 0.0 2.0 4.0 6.0 NaN
1 9.0 NaN 13.0 15.0 NaN
2 18.0 20.0 22.0 24.0 NaN
3 NaN NaN NaN NaN NaN
Otros ejemplos:
df_a - df_b
a b c d e
0 0.0 0.0 0.0 0.0 NaN
1 -1.0 NaN -1.0 -1.0 NaN
2 -2.0 -2.0 -2.0 -2.0 NaN
3 NaN NaN NaN NaN NaN
df_a*df_b
a b c d e
0 0.0 1.0 4.0 9.0 NaN
1 20.0 NaN 42.0 56.0 NaN
2 80.0 99.0 120.0 143.0 NaN
3 NaN NaN NaN NaN NaN
df_a/2
a b c d
0 0.0 0.5 1.0 1.5
1 2.0 2.5 3.0 3.5
2 4.0 4.5 5.0 5.5
Exiten métodos para realizar operaciones mátematica:
| métodos | Operacion |
|---|---|
| add, radd | Método para la adición (+) |
| sub, rsub | Método para la sustracción (-) |
| div, rdiv | Método para la división (/) |
| floordiv, rfloordiv | Método para la división (//) |
| mul, rmul | Método para la multiplicación (*) |
| pow, rpow | Método para el exponente (**) |
En el caso de la adición:
df_a.add(df_b, fill_value = 0)
a b c d e
0 0.0 2.0 4.0 6.0 4.0
1 9.0 5.0 13.0 15.0 9.0
2 18.0 20.0 22.0 24.0 14.0
3 15.0 16.0 17.0 18.0 19.0
Es importante notar que los NaN son reemplazados por el valor correspondiente según el orden de los datos.
Se crea un DataFrame:
data_df = pd.DataFrame(np.random.randn(4, 3), columns=list('abc'), index=['Santiago', 'Coquimbo', 'Valparaiso', 'Chillan'])
data_df
a b c
Santiago 0.369482 0.053679 1.421907
Coquimbo 0.299947 1.853414 0.549716
Valparaiso 0.372163 -1.239573 -1.546662
Chillan 0.920469 0.751526 0.196118
np.abs(data_df) # método para el valor absoluto
a b c
Santiago 0.369482 0.053679 1.421907
Coquimbo 0.299947 1.853414 0.549716
Valparaiso 0.372163 1.239573 1.546662
Chillan 0.920469 0.751526 0.196118
Creamos una función anónima:
funcion_anonima = lambda x: x.mean()
Luego la aplicamos al DataFrame (hay que tener cuidado con el axis):
data_df.apply(funcion_anonima, axis= 0) # por columna
a 0.490515
b 0.354761
c 0.155270
dtype: float64
data_df.apply(funcion_anonima, axis= 1) # por fila
Santiago 0.615023
Coquimbo 0.901026
Valparaiso -0.804691
Chillan 0.622704
dtype: float64
Si se construye una función que retorne una Series apartir de DataFrame:
def f_min_max_mean(x):
return pd.Series([x.mean(), x.min(), x.max()], index=['mean', 'max', 'min'])
data_df.apply(f_min_max_mean)
a b c
mean 0.490515 0.354761 0.155270
max 0.299947 -1.239573 -1.546662
min 0.920469 1.853414 1.421907
Cuando se usa una función a un DataFrame, que va por fila y por columna (se aplica a todo el DataFrame), hay que usar .applymap():
formato = lambda x: '%.2f' % x
data_df.applymap(formato)
a b c
Santiago 0.37 0.05 1.42
Coquimbo 0.30 1.85 0.55
Valparaiso 0.37 -1.24 -1.55
Chillan 0.92 0.75 0.20
La versión de .applymap() para Series, es .map():
data_df['a'].map(formato)
Santiago 0.37
Coquimbo 0.30
Valparaiso 0.37
Chillan 0.92
Name: a, dtype: object
Para realizar estadística descriptiva usando Pandas, se puede utilizar los métodos (axis=0 por filas, axis=1 por columnas) incluidos en los objetos de pandas:
data_df.sum()
a 1.962060
b 1.419046
c 0.621079
dtype: float64
data_df.cumsum()
a b c
Santiago 0.369482 0.053679 1.421907
Coquimbo 0.669429 1.907094 1.971624
Valparaiso 1.041592 0.667520 0.424961
Chillan 1.962060 1.419046 0.621079
data_df.mean()
a 0.490515
b 0.354761
c 0.155270
dtype: float64
data_df.max()
a 0.920469
b 1.853414
c 1.421907
dtype: float64
data_df.min()
a 0.299947
b -1.239573
c -1.546662
dtype: float64
O utilizar el métodos describe():
data_df.describe()
a b c
count 4.000000 4.000000 4.000000
mean 0.490515 0.354761 0.155270
std 0.288579 1.295626 1.246087
min 0.299947 -1.239573 -1.546662
25% 0.352098 -0.269634 -0.239577
50% 0.370822 0.402602 0.372917
75% 0.509239 1.026998 0.767764
max 0.920469 1.853414 1.421907
Utilizando NumPy genere un Pandas con estructura Series con 10 datos aleatorios.
Cambie el index de la Series por letras.
Agregue dos nuevos números con su respectivo index.
Genere una Series a partir del siguiente diccionario:
{'Santiago': 404495, 'Providencia': 142079, 'Huechuraba': 98671, 'Quilicura': 210410}
Genere otra series con el diccionario anterior pero que el index sea: Santiago, Providencia, Huechuraba, San Miguel. ¿Que se observa?
Utilizando Pandas genere los siguientes DataFrames:
| a | b | c | d |
|---|---|---|---|
| 0 | 1 | 2 | 3 |
| 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 |
| a | b | c | d | e |
|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 |
| 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 |
Para cada DataFrame creado en (1):
Seleccione la primera fila de cada columna.
Seleccione la columna c y d.
Seleccione la columna a y b, luego filtre los valores menores a 5 de la columna a.
Sume los dos DataFrames.
A partir de la pregunta anterior, reemplace por cero aquellos valores con NaN.
Utilizando NumPy genere un DataFrame que contenga 4 filas y 3 columnas, los datos deben ser aleatorios y aceptar negativos.
Obtenga el valor absoluto de cada observación.
Utilizando una función anónima calcule el promedio de cada columna.
Construya una función que permite calcular el promedio, el valor mínimo y máximo de cada columna.
Importe la base de datos credits.csv.
Realice la estadística descriptiva.
Seleccione aquellas observaciones que sean del género (‘Gender’) femenino (‘Female’).
Muestre los individuos que:
Posean una renta mensual mayor a 1000.
Posean una renta mensual mayor a 1000 y que sean del género femenino.
Posean una renta mensual mayor a 1000 o que sean del género femenino.
Ordene los datos de mayor a menor según ingresos, muestre las 10 primeras observaciones.