Introducción a Pandas

El post introduce la librería Pandas para la construcción y manipulación de estructuras de datos basados en Series y DataFrame.

Gabriel Cabrera true
08-10-2019

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

Estructura de datos

En pandas existen dos estructura de datos principales:

  1. Series: es un array de una dimensión (one-dimensional array) con una secuancia de valores y asociado una etiqueta (label) denominada index.

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

Series

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

DataFrame

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

Funcionalidades Esenciales

Reindexing

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

Eliminar Valores desde el Eje (Axis)

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

Indexar, Seleccinar, y Filtrar

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

Seleccionar con loc e iloc

Pandas 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

Operatorias matemáticas y llenado de NAs

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

Aplicación de Funciones y Mapping

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

Estadística Descriptiva

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

Ejercicios

  1. Utilizando NumPy genere un Pandas con estructura Series con 10 datos aleatorios.

  2. Cambie el index de la Series por letras.

  3. Agregue dos nuevos números con su respectivo index.

  4. Genere una Series a partir del siguiente diccionario:

    {'Santiago': 404495, 'Providencia': 142079, 'Huechuraba': 98671, 'Quilicura': 210410}

  5. Genere otra series con el diccionario anterior pero que el index sea: Santiago, Providencia, Huechuraba, San Miguel. ¿Que se observa?

  6. Utilizando Pandas genere los siguientes DataFrames:

Table 1: DataFrame A
a b c d
0 1 2 3
4 5 6 7
8 9 10 11
Table 1: DataFrame B
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
  1. Para cada DataFrame creado en (1):

    1. Seleccione la primera fila de cada columna.

    2. Seleccione la columna c y d.

    3. Seleccione la columna a y b, luego filtre los valores menores a 5 de la columna a.

  2. Sume los dos DataFrames.

  3. A partir de la pregunta anterior, reemplace por cero aquellos valores con NaN.

  4. Utilizando NumPy genere un DataFrame que contenga 4 filas y 3 columnas, los datos deben ser aleatorios y aceptar negativos.

    1. Obtenga el valor absoluto de cada observación.

    2. Utilizando una función anónima calcule el promedio de cada columna.

    3. Construya una función que permite calcular el promedio, el valor mínimo y máximo de cada columna.

  5. Importe la base de datos credits.csv.

  6. Realice la estadística descriptiva.

  7. Seleccione aquellas observaciones que sean del género (‘Gender’) femenino (‘Female’).

  8. Muestre los individuos que:

    1. Posean una renta mensual mayor a 1000.

    2. Posean una renta mensual mayor a 1000 y que sean del género femenino.

    3. Posean una renta mensual mayor a 1000 o que sean del género femenino.

    4. Ordene los datos de mayor a menor según ingresos, muestre las 10 primeras observaciones.

Enlaces