Pandas: DataFrames

Se importa la biblioteca pandas, para disponer de todos los objetos aquí definidos:


In [46]:
import pandas as pd

# importar numpy para usarlo en los ejemplos
import numpy as np

DataFrames

Un DataFrame es una generalización del concepto de Serie. Contiene un índice (numérico, nominal, fechas, etc.) y varias series cuyos datos se 'alinean' de acuerdo con los índices. Se puede asimilar al concepto de una tabla de varias columnas, donde los índices referencian las filas y los títulos de las columnas las series. En el lenguaje del aprendizaje de máquina a las columnas se les denomina características (features) del dataset.

Siendo una colección de series, una primera forma de instanciar un DataFrame es a partir de varias series, por ejemplo:


In [47]:
# Se crean un par de series. Observar que no es necesario que los indices coincidan
s1 = pd.Series(np.random.randn(4), index=['a', 'b', 'c', 'd'])
s2 = pd.Series(np.random.randn(4), index=['b', 'c', 'd', 'e'])

# Se crea un diccionario con las 2 columnas (Series)
dict = { 'one': s1, 'two': s2 }

# Se crea un objeto DataFrame a partir de estos datos
df1 = pd.DataFrame(dict)
df1


Out[47]:
one two
a 0.192519 NaN
b -1.077807 0.309222
c 0.442034 -0.950180
d 0.456930 -0.222042
e NaN -0.247909

Observar que a los indices no presentes en alguna de las series se les asigna el valor NaN (Not a Number)

Otra alternativa es crear el DataFrame a partir de un ndarray de numpy. En este caso los nombres de las columnas se deben especificar por medio del parametro columns. Por ejemplo:


In [48]:
df2 = pd.DataFrame( np.random.randn(7,4), columns=[ 'arbol', 'fruta', 'flor', 'raiz' ] )
df2


Out[48]:
arbol fruta flor raiz
0 0.502291 -0.509780 -0.204952 0.606116
1 1.075902 0.438908 0.162039 -1.018911
2 -1.035007 -0.054078 -1.875182 0.161309
3 -0.406954 0.875528 -0.170854 0.285006
4 0.822018 -1.594313 0.502052 0.405813
5 -0.228217 -0.923661 0.094314 0.074764
6 0.746371 -0.211840 0.326609 0.086460

Se puede crear a partir de un diccionario de listas o de ndarrays. El argumento index permite especificar los indices de la tabla.


In [49]:
dict = {'one' : [1., 2., 3., 4.],
     'two' : [4., 3., 2., 1.]}
df3 = pd.DataFrame(dict, index=['qwe','rty','uio','pas'])
df3


Out[49]:
one two
qwe 1 4
rty 2 3
uio 3 2
pas 4 1

o asi mismo de una lista de diccionarios. El argumento index permite especificar los indices de la tabla.


In [50]:
listdict = [ {'b':1,'c':2,'d':3}, {'a':1.1,'c':1.2,'d':1.3}, {'c':2.1,'d':2.2,'b':2.2}, {'a':0,'b':0.1,'c':0.2,'d':0.3} ]
df4 = pd.DataFrame(listdict, index=['2017-10-01','2017-11-01','2017-12-01','2018-01-01'])
df4


Out[50]:
a b c d
2017-10-01 NaN 1.0 2.0 3.0
2017-11-01 1.1 NaN 1.2 1.3
2017-12-01 NaN 2.2 2.1 2.2
2018-01-01 0.0 0.1 0.2 0.3

Observar en el ejemplo anterior como los datos se alinean por los nombres de las columnas.

Atributos de los DataFrames


In [51]:
# Tipo de dato de cada columna
df4.dtypes


Out[51]:
a    float64
b    float64
c    float64
d    float64
dtype: object

In [52]:
# Los valores del dataframe. Observar que retorna un ndarray
df3.values


Out[52]:
array([[ 1.,  4.],
       [ 2.,  3.],
       [ 3.,  2.],
       [ 4.,  1.]])

In [53]:
# Nombres de las columnas
df4.columns


Out[53]:
Index(['a', 'b', 'c', 'd'], dtype='object')

In [54]:
# Indices de la tabla
df4.index


Out[54]:
Index(['2017-10-01', '2017-11-01', '2017-12-01', '2018-01-01'], dtype='object')

In [55]:
# Esta vacia la estructura?
df4.empty


Out[55]:
False

In [56]:
# Obtener el tamaño del DataFrame
df4.size


Out[56]:
16

In [57]:
# Obtener la forma del DataFrame
df4.shape


Out[57]:
(4, 4)

Acceso a los elementos de un DataFrame

Se puede utilizar el método .get o la notación de indices, por ejemplo:


In [58]:
df3.get('one')


Out[58]:
qwe    1
rty    2
uio    3
pas    4
Name: one, dtype: float64

In [59]:
df3['one']


Out[59]:
qwe    1
rty    2
uio    3
pas    4
Name: one, dtype: float64

Se puede recuperar un subconjunto de columnas


In [60]:
df4[ ['b','c'] ]


Out[60]:
b c
2017-10-01 1.0 2.0
2017-11-01 NaN 1.2
2017-12-01 2.2 2.1
2018-01-01 0.1 0.2

Recuperar un subconjunto de filas (índices)


In [61]:
df4.loc[ ['2017-12-01', '2017-10-01'], :]


Out[61]:
a b c d
2017-12-01 NaN 2.2 2.1 2.2
2017-10-01 NaN 1.0 2.0 3.0

O que tal un subconjunto de filas y de columnas


In [62]:
df4.loc[ ['2017-12-01', '2017-10-01'], ['d','b'] ]


Out[62]:
d b
2017-12-01 2.2 2.2
2017-10-01 3.0 1.0

Tambien es posible referenciar los elementos del DataFrame por el numero de fila y columna que les corresponde


In [63]:
df4.iloc[0,0], df4.iloc[2,3]


Out[63]:
(nan, 2.2000000000000002)

Por rangos de numeros de filas o de columnas


In [64]:
df2.iloc[5:,:]


Out[64]:
arbol fruta flor raiz
5 -0.228217 -0.923661 0.094314 0.074764
6 0.746371 -0.211840 0.326609 0.086460

In [65]:
df2.iloc[3:,:2]


Out[65]:
arbol fruta
3 -0.406954 0.875528
4 0.822018 -1.594313
5 -0.228217 -0.923661
6 0.746371 -0.211840

In [66]:
df2.iloc[4:6, 1:2]


Out[66]:
fruta
4 -1.594313
5 -0.923661

Para asignar valores a una celda del dataframe, se puede utilizar .at con los nombres de indices y columnas, o .iat con las posiciones numericas correspondientes


In [67]:
df2.at[4,'fruta'] = 555
df2.iat[4,0] = 666
df2.loc[4,:]


Out[67]:
arbol    666.000000
fruta    555.000000
flor       0.502052
raiz       0.405813
Name: 4, dtype: float64

Hacer una copia de un DataFrame


In [68]:
df5 = df4.copy()
df5


Out[68]:
a b c d
2017-10-01 NaN 1.0 2.0 3.0
2017-11-01 1.1 NaN 1.2 1.3
2017-12-01 NaN 2.2 2.1 2.2
2018-01-01 0.0 0.1 0.2 0.3

Reemplazar los NaN por un valor deseado. El parametro inplace=True hace que se modifique el dataframe, en lugar de devolver una copia con los cambios.


In [69]:
df5.fillna(value=-999, inplace=True)
df5


Out[69]:
a b c d
2017-10-01 -999.0 1.0 2.0 3.0
2017-11-01 1.1 -999.0 1.2 1.3
2017-12-01 -999.0 2.2 2.1 2.2
2018-01-01 0.0 0.1 0.2 0.3

Otra alternativa para manejar la presencia de NaNs es suprir las filas,columnas donde aparezcan


In [70]:
df5 = df4.copy()
df5.dropna(inplace=True)
df5


Out[70]:
a b c d
2018-01-01 0 0.1 0.2 0.3

Para eliminar algunas filas del DataFrame se utiliza el método .drop


In [71]:
df4.drop(['2017-12-01', '2017-11-01'])


Out[71]:
a b c d
2017-10-01 NaN 1.0 2.0 3.0
2018-01-01 0 0.1 0.2 0.3

Observar en el ejemplo anterior que toda fila o toda columna que contenga un NaN es eliminada.

Se pueden recuperar los elementos del comienzo o del final del DataFrame


In [72]:
df4.head(2)


Out[72]:
a b c d
2017-10-01 NaN 1 2.0 3.0
2017-11-01 1.1 NaN 1.2 1.3

In [73]:
df4.tail(3)


Out[73]:
a b c d
2017-11-01 1.1 NaN 1.2 1.3
2017-12-01 NaN 2.2 2.1 2.2
2018-01-01 0.0 0.1 0.2 0.3

Indexado lógico

En forma similar a como lo permiten las series es posible indexar los elementos de un DataFrame usando condiciones lógicas. Por ejemplo:


In [74]:
df4[ df4>2 ]


Out[74]:
a b c d
2017-10-01 NaN NaN NaN 3.0
2017-11-01 NaN NaN NaN NaN
2017-12-01 NaN 2.2 2.1 2.2
2018-01-01 NaN NaN NaN NaN

In [75]:
df4.isnull()


Out[75]:
a b c d
2017-10-01 True False False False
2017-11-01 False True False False
2017-12-01 True False False False
2018-01-01 False False False False

In [76]:
# Otra forma de hacer el .fillna
df4[ df4.isnull() ] = 0
df4


Out[76]:
a b c d
2017-10-01 0.0 1.0 2.0 3.0
2017-11-01 1.1 0.0 1.2 1.3
2017-12-01 0.0 2.2 2.1 2.2
2018-01-01 0.0 0.1 0.2 0.3

In [77]:
# .all aplica la condicion booleana a todos los elementos de cada columna
(df4>0).all()


Out[77]:
a    False
b    False
c     True
d     True
dtype: bool

In [78]:
# .any comprueba la condicion boolean si al menos un elemento de cada serie la cumple
(df4==0).any()


Out[78]:
a     True
b     True
c    False
d    False
dtype: bool

Operaciones aritméticas

Para DataFrames cuyos elementos son numéricos, se dispone de todos los metodos para realizar las operaciones aritmeticas estandar: .add .sub .mul .div .pow .mod .abs .dot . Asi mismo, se pueden utilizar los operadores sobrecargados: + - * / **


In [79]:
df5**2 - df4/2


Out[79]:
a b c d
2017-10-01 NaN NaN NaN NaN
2017-11-01 NaN NaN NaN NaN
2017-12-01 NaN NaN NaN NaN
2018-01-01 0 -0.04 -0.06 -0.06

Operadores relacionales

Los operadores relacionales se encuentran disponibles como métodos o a través de los operadores sobrecargados. Los métodos son .eq .ne .ge .gt .le .lt y sus correspondientes operadores == != >= > <= <


In [85]:
df4<=df4**2


Out[85]:
a b c d
2017-10-01 True True True True
2017-11-01 True True True True
2017-12-01 True True True True
2018-01-01 True False False False

Operaciones estadísticas

Las operaciones estadísticas con un DataFrame son: .describe .count .mix .max .mean .median .mode .var .std .cov .corr .sem


In [86]:
df4.describe()


Out[86]:
a b c d
count 4.000 4.000000 4.000000 4.000000
mean 0.275 0.825000 1.375000 1.700000
std 0.550 1.021029 0.880814 1.163329
min 0.000 0.000000 0.200000 0.300000
25% 0.000 0.075000 0.950000 1.050000
50% 0.000 0.550000 1.600000 1.750000
75% 0.275 1.300000 2.025000 2.400000
max 1.100 2.200000 2.100000 3.000000

In [87]:
df4.mean()


Out[87]:
a    0.275
b    0.825
c    1.375
d    1.700
dtype: float64

In [88]:
df4.var()


Out[88]:
a    0.302500
b    1.042500
c    0.775833
d    1.353333
dtype: float64

In [89]:
df4.cov()


Out[89]:
a b c d
a 0.302500 -0.302500 -0.064167 -0.146667
b -0.302500 1.042500 0.700833 0.753333
c -0.064167 0.700833 0.775833 0.963333
d -0.146667 0.753333 0.963333 1.353333

Notar que efectivamente la diagonal de la matriz de covarianza son las varianzas

Operaciones de entrada/salida

Salvar, recupear un DataFrame desde un archivo separado por comas


In [90]:
df4.to_csv('Dataframe4.csv')
df6 = pd.read_csv('Dataframe4.csv', index_col=0)
df6


Out[90]:
a b c d
2017-10-01 0.0 1.0 2.0 3.0
2017-11-01 1.1 0.0 1.2 1.3
2017-12-01 0.0 2.2 2.1 2.2
2018-01-01 0.0 0.1 0.2 0.3

Se puede obtener la grafica de las series que componen el DataFrame en MatPlotLib


In [91]:
import matplotlib.pyplot as plt
df6.plot()
plt.show()


Obtener el histograma de cada serie


In [92]:
df6.hist()
plt.show()


Leer tablas Excel

Pandas también ofrece métodos para leer tablas de Excel. Para este fin se debe tener instalado el módulo xlrd.


In [93]:
t = pd.read_excel('titanic.xls')
t.shape


Out[93]:
(1309, 14)

In [95]:
t.head()


Out[95]:
pclass survived name sex age sibsp parch ticket fare cabin embarked boat body home.dest
0 1 1 Allen, Miss. Elisabeth Walton female 29.0000 0 0 24160 211.3375 B5 S 2 NaN St Louis, MO
1 1 1 Allison, Master. Hudson Trevor male 0.9167 1 2 113781 151.5500 C22 C26 S 11 NaN Montreal, PQ / Chesterville, ON
2 1 0 Allison, Miss. Helen Loraine female 2.0000 1 2 113781 151.5500 C22 C26 S NaN NaN Montreal, PQ / Chesterville, ON
3 1 0 Allison, Mr. Hudson Joshua Creighton male 30.0000 1 2 113781 151.5500 C22 C26 S NaN 135 Montreal, PQ / Chesterville, ON
4 1 0 Allison, Mrs. Hudson J C (Bessie Waldo Daniels) female 25.0000 1 2 113781 151.5500 C22 C26 S NaN NaN Montreal, PQ / Chesterville, ON

Operación join

La operación join permite combinar dos dataframes alineando sus indices. Un left-join preserva los índices del DataFrame izquierdo, un right-join preserva los índices del DataFrame derecho, un inner-join preserva la intersección de índices de ambos DataFrames y el outer-join preserva la union de los índices.

Considere los siguientes DataFrames que no coinciden en todos sus índices:


In [97]:
indices = np.arange( '2017-01-01', '2017-01-05', dtype=np.datetime64 )
df1 = pd.DataFrame(
    { 'col1':pd.Series([10,20,30,40], index=indices),
      'col2':pd.Series([50,60,70,80], index=indices) } )
df1


Out[97]:
col1 col2
2017-01-01 10 50
2017-01-02 20 60
2017-01-03 30 70
2017-01-04 40 80

In [100]:
df2 = pd.DataFrame(
    { 'col3':pd.Series([70,80,90,100], index=np.arange( '2017-01-02', '2017-01-06', dtype=np.datetime64 ) ) } )
df2


Out[100]:
col3
2017-01-02 70
2017-01-03 80
2017-01-04 90
2017-01-05 100

Los distintos tipos de join serían


In [102]:
left = df1.join(df2)
left


Out[102]:
col1 col2 col3
2017-01-01 10 50 NaN
2017-01-02 20 60 70
2017-01-03 30 70 80
2017-01-04 40 80 90

In [105]:
right = df1.join(df2, how='right')
right


Out[105]:
col1 col2 col3
2017-01-02 20 60 70
2017-01-03 30 70 80
2017-01-04 40 80 90
2017-01-05 NaN NaN 100

In [107]:
inner = df1.join(df2, how='inner')
inner


Out[107]:
col1 col2 col3
2017-01-02 20 60 70
2017-01-03 30 70 80
2017-01-04 40 80 90

In [109]:
outer = df1.join(df2, how='outer')
outer


Out[109]:
col1 col2 col3
2017-01-01 10 50 NaN
2017-01-02 20 60 70
2017-01-03 30 70 80
2017-01-04 40 80 90
2017-01-05 NaN NaN 100

Concatenación de DataFrames

La operación de concatenación permite agregar filas a una tabla, alineando las columnas. Acepta una lista de DataFrames a concatenar.


In [111]:
pd.concat( [df1,df2] )


Out[111]:
col1 col2 col3
2017-01-01 10 50 NaN
2017-01-02 20 60 NaN
2017-01-03 30 70 NaN
2017-01-04 40 80 NaN
2017-01-02 NaN NaN 70
2017-01-03 NaN NaN 80
2017-01-04 NaN NaN 90
2017-01-05 NaN NaN 100

In [113]:
indices = np.arange( '2017-01-05', '2017-01-08', dtype=np.datetime64 )
df3 = pd.DataFrame(
    { 'col1':pd.Series([41,42,43], index=indices),
      'col2':pd.Series([81,82,83], index=indices) } )
df3


Out[113]:
col1 col2
2017-01-05 41 81
2017-01-06 42 82
2017-01-07 43 83

In [115]:
pd.concat( [df1,df3] )


Out[115]:
col1 col2
2017-01-01 10 50
2017-01-02 20 60
2017-01-03 30 70
2017-01-04 40 80
2017-01-05 41 81
2017-01-06 42 82
2017-01-07 43 83

La operación append también realiza una función similar a la concatenación


In [117]:
df1.append([df2,df3])


Out[117]:
col1 col2 col3
2017-01-01 10 50 NaN
2017-01-02 20 60 NaN
2017-01-03 30 70 NaN
2017-01-04 40 80 NaN
2017-01-02 NaN NaN 70
2017-01-03 NaN NaN 80
2017-01-04 NaN NaN 90
2017-01-05 NaN NaN 100
2017-01-05 41 81 NaN
2017-01-06 42 82 NaN
2017-01-07 43 83 NaN

Merge

La operación merge es una generalización del join donde se utiliza un atributo cualquiera para efecto de alinear las filas de las tablas. Los índices se ignoran y son descartados por la operación merge.


In [118]:
indices = np.arange( '2017-01-01', '2017-01-05', dtype=np.datetime64 )
df1 = pd.DataFrame(
    { 'col1' :pd.Series([10,20,30,40], index=indices),
      'col2' :pd.Series([50,60,70,80], index=indices),
      'llave':pd.Series([1,2,3,4],     index=indices) } )
df1


Out[118]:
col1 col2 llave
2017-01-01 10 50 1
2017-01-02 20 60 2
2017-01-03 30 70 3
2017-01-04 40 80 4

In [119]:
indices = np.arange( '2017-01-02', '2017-01-06', dtype=np.datetime64 )
df2 = pd.DataFrame(
    { 'col3' :pd.Series([70,80,90,100], index=indices ),
      'llave':pd.Series([2,1,4,3],     index=indices)    } )
df2


Out[119]:
col3 llave
2017-01-02 70 2
2017-01-03 80 1
2017-01-04 90 4
2017-01-05 100 3

In [120]:
pd.merge(df1, df2, on='llave')


Out[120]:
col1 col2 llave col3
0 10 50 1 80
1 20 60 2 70
2 30 70 3 100
3 40 80 4 90

Referencias