Conociendo Pandas. Parte 1.

En este artículo conoceremos la librería Pandas, que es fundamental en Python para el análisis de datos, y veremos como extraer datos de un dataframe.

Recuerda unirte a nuestro canal en Telegram de Python Para Trading

Pandas nos hace la vida un poco más fácil.

Muchos estamos familiarizados con Excel y con sus hojas de cálculos pues son ampliamente utilizadas para almacenar y manipular datos, especialmente numéricos, y hacer cálculos.

En Python tenemos varias librerías para manipular todo tipo de datos, pero una es especialmente eficiente para las series temporales, Pandas.

Pandas tiene dos objetos básicos las series, que son vectores indexados y los dataframes, que son matrices n-dimensionales indexadas. Podemos pensar en un dataframe como una hoja de calculo de Excel supervitaminada.

Una serie temporal es una secuencia de datos tomados en determinados momentos ordenados cronológicamente. Por ejemplo la cotización de cierre de una acción, tenemos una sucesión de valores medidos al final de la sesión de cada día. En análisis financiero prácticamente tratamos con series temporales casí todo el tiempo, y Pandas nos hace la vida mucho mas fácil.

De hecho Pandas fue desarrollada inicialmente por Wes McKinney cuando trabajaba en un hedge found, AQR Capital Management, y posteriormente liberada como open source.

Esta librería tiene un gran número de funciones para realizar cálculos y manipulaciones de series y dataframes, y se ha originado un ecosistema al ser usada como base por otras librerías de terceros, que hacen uso de estos objetos y sus funciones.

Si has instalado Anaconda para usar Python, el módulo de Pandas ya viene preinstalado. Si no es así puedes instalarlo usando conda, basta con ejecutar en el terminal de comandos:

conda install pandas

o bien usando pip:

pip install pandas


Dataframes

Un DataFrame de Pandas es una matriz ordenada que puede contener cualquier tipo de datos. Se ordena en columnas, que deben contener siempre un mismo tipo de dato, y en filas que generan un indice.

Para practicar con los dataframes vamos a usar series temporales de cotizaciones diarias de acciones. Para lo cual nos descargaremos los datos históricos.

Pandas Datareader es un módulo asociado a Pandas que permite descargar datos desde distintas fuentes.

Sin embargo, debido a que Yahoo descontinuó su API para datos financieros, para poder descarga desde Yahoo Finance correctamente, utilizaremos el módulo Fix Yahoo Finance. Si no lo tienes instalado puedes instalarlo con :

pip install fix-yahoo-finance

Vamos a comenzar importando esos dos módulos. Importamos Pandas y para simplificar le asignamos el nombre pd, igual hacemos con Fix Yahoo Finance.

In [1]:
import pandas as pd
import fix_yahoo_finance as yf

Descargaremos el histórico diario de Santander y BBVA desde principios de 2008 hasta final de 2018, once años. Para ello pasaremos como parámetros los tickers de los valores y las fechas de inicio y fin. Los tickers para otros valores se pueden localizar en la página de Yahoo Finance.

En un futuro artículo hablaremos sobre las fuentes de datos financieros y sus API, pero de de momento nos bastará con saber como bajar los datos que necesitamos para estudiar los dataframe de Pandas.

In [3]:
start = '2008-01-01'
end = '2018-12-31'
tickers=['SAN.MC', 'BBVA.MC']
In [5]:
san = yf.download(tickers[0], start=start, end=end)
[*********************100%***********************]  1 of 1 downloaded
In [7]:
bbva = yf.download(tickers[1], start=start, end=end)
[*********************100%***********************]  1 of 1 downloaded

Lo que recibimos en la variable san es un DataFrame de Pandas, una matriz de dos dimensiones, como dijimos algo similar al un hoja de calculo de Excel.

Vamos a ver usando el método head() las cinco primeras lineas del dataframe san.

In [12]:
san.head()
Out[12]:
OpenHighLowCloseAdj CloseVolume
Date
2008-01-0213.248913.330213.095313.17665.868722103998100
2008-01-0313.140513.203712.968913.05025.812426113222703
2008-01-0413.005013.086312.707012.86055.727935100543802
2008-01-0712.815412.905712.688912.77935.69176871995342
2008-01-0812.788312.806312.499312.58065.60327083100401

Y con tail() las cinco últimas de bbva.

In [13]:
bbva.tail()
Out[13]:
OpenHighLowCloseAdj CloseVolume
Date
2018-12-214.63304.66004.53404.61854.48402296489872
2018-12-244.57454.59904.54254.55804.4252835652399
2018-12-274.60004.63804.47654.54504.41266232726196
2018-12-284.55854.64804.55004.64104.50586620924025
2018-12-314.63504.66354.62004.63554.5005266885185

Vamos a usar la función de Pandas describe(), que nos dará un resumen estadístico de los datos contenidos en el dataframe. Lo que este método nos devuelve es un nuevo dataframe con los datos agregados.

In [9]:
san.describe()
Out[9]:
OpenHighLowCloseAdj CloseVolume
count2810.0000002810.0000002810.0000002810.0000002810.0000002.810000e+03
mean6.6460346.7363546.5399296.6418254.4652919.608371e+07
std2.1211962.1481942.0857752.1201260.8735867.419585e+07
min3.1178703.3273703.0982003.2457301.8784047.377082e+06
25%5.2162905.2809585.1343705.2035023.7822405.309241e+07
50%6.0193606.0910005.9205156.0287054.5497487.453013e+07
75%7.7403277.8327937.6342957.7388605.2126891.127219e+08
max13.24890013.33020013.09530013.1766006.1571579.092368e+08
In [10]:
bbva.describe()
Out[10]:
OpenHighLowCloseAdj CloseVolume
count2810.0000002810.0000002810.0000002810.0000002810.0000002.810000e+03
mean7.9734808.0780997.8495247.9689025.6471235.260945e+07
std2.1162392.1337732.0904412.1133491.1281754.675970e+07
min4.4000004.5400004.2753104.4300002.5500835.217450e+06
25%6.3815006.4655006.2870006.3750004.7577892.698997e+07
50%7.6095007.6995007.4720007.5975005.6423543.900200e+07
75%9.0425009.1575008.9117509.0404506.6577786.072695e+07
max16.10210016.15970015.85230015.9291008.3306986.009019e+08

Vemos que tanto en este caso, como al usar head() y tail() no hemos aplicado la función y pasado entre paréntesis el dataframe como un parámetros, sino que tras el nombre del dataframe con un punto pasamos la función. Esto es debido a que describe() es un método del objeto dataframe, y ya lo toma como parámetro. Es especialmente útil y permite el encadenado de métodos, es decir ir agregando funciones que toman el resultado de la anterior como parámetro.

Otro método que nos proporciona información resumida es info().

In [14]:
san.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 2810 entries, 2008-01-02 to 2018-12-31
Data columns (total 6 columns):
Open         2810 non-null float64
High         2810 non-null float64
Low          2810 non-null float64
Close        2810 non-null float64
Adj Close    2810 non-null float64
Volume       2810 non-null int64
dtypes: float64(5), int64(1)
memory usage: 153.7 KB
In [15]:
bbva.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 2810 entries, 2008-01-02 to 2018-12-31
Data columns (total 6 columns):
Open         2810 non-null float64
High         2810 non-null float64
Low          2810 non-null float64
Close        2810 non-null float64
Adj Close    2810 non-null float64
Volume       2810 non-null int64
dtypes: float64(5), int64(1)
memory usage: 153.7 KB

Revisamos las dimensiones de cada dataframe, vemos que tienen dos dimensiones, que reflejan las filas y las columnas.

2810 filas correspondientes a los días de cotización, y 6 columnas para los distintos datos.

Para ello usamos shape, que como vemos no lleva paréntesis detrás, esto es debido a que es una propiedad del dataframe y no un método del mismo.

In [17]:
san.shape
Out[17]:
(2810, 6)
In [18]:
bbva.shape
Out[18]:
(2810, 6)

Podemos extraer el índice y las columnas de un dataframe usando index y columns respectivamente.

El índice de nuestros dataframe son las fechas de los días cotizados.

In [83]:
san.index
Out[83]:
DatetimeIndex(['2008-01-02', '2008-01-03', '2008-01-04', '2008-01-07',
               '2008-01-08', '2008-01-09', '2008-01-10', '2008-01-11',
               '2008-01-14', '2008-01-15',
               ...
               '2018-12-14', '2018-12-17', '2018-12-18', '2018-12-19',
               '2018-12-20', '2018-12-21', '2018-12-24', '2018-12-27',
               '2018-12-28', '2018-12-31'],
              dtype='datetime64[ns]', name='Date', length=2810, freq=None)

Mientras que las columnas recogen los distintos valores de cada día cotizado.

In [13]:
san.columns
Out[13]:
Index(['Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume'], dtype='object')

Observamos como en ambos casos nos muestra 6 columnas y 5 filas. Las columna Open por ejemplo contiene todos los precios de apertura de cada acción. Y las filas están precedidas por una fecha que es el índice de nuestro dataframe. Así podemos obtener datos llamándolos por su columna, su índice o por una combinación de ambos.

Extracción de datos.

Puede ser que nos interese solo una columna y podemos extraerla del dataframe, lo que nos devuelve es un objeto Serie de Pandas.

In [24]:
san['Close']
Out[24]:
Date
2008-01-02    13.1766
2008-01-03    13.0502
2008-01-04    12.8605
2008-01-07    12.7793
2008-01-08    12.5806
2008-01-09    12.3277
2008-01-10    12.1832
2008-01-11    12.3819
2008-01-14    12.2103
2008-01-15    11.7407
2008-01-16    11.6052
2008-01-17    11.5149
2008-01-18    11.2349
2008-01-21    10.2234
2008-01-22    10.6930
2008-01-23    10.1782
2008-01-24    10.8466
2008-01-25    10.9188
2008-01-28    10.8285
2008-01-29    10.8917
2008-01-30    10.8466
2008-01-31    10.6840
2008-02-01    10.9007
2008-02-04    10.8285
2008-02-05    10.2505
2008-02-06    10.4763
2008-02-07    10.4763
2008-02-08    10.5034
2008-02-11    10.3227
2008-02-12    10.7111
               ...   
2018-11-16     4.2200
2018-11-19     4.2030
2018-11-20     4.0800
2018-11-21     4.1530
2018-11-22     4.0855
2018-11-23     4.0875
2018-11-26     4.2055
2018-11-27     4.1985
2018-11-28     4.2245
2018-11-29     4.2135
2018-11-30     4.1850
2018-12-03     4.2720
2018-12-04     4.1675
2018-12-05     4.1390
2018-12-06     3.9800
2018-12-07     3.9790
2018-12-10     3.8730
2018-12-11     3.8790
2018-12-12     4.0105
2018-12-13     4.0710
2018-12-14     4.0595
2018-12-17     4.0270
2018-12-18     3.9900
2018-12-19     4.0415
2018-12-20     3.9780
2018-12-21     3.9305
2018-12-24     3.8915
2018-12-27     3.8620
2018-12-28     3.9450
2018-12-31     3.9730
Name: Close, Length: 2810, dtype: float64

O bien solo nos interese una fecha en concreto.

In [27]:
bbva.loc['2018-12-31']
Out[27]:
Open         4.635000e+00
High         4.663500e+00
Low          4.620000e+00
Close        4.635500e+00
Adj Close    4.500526e+00
Volume       6.885185e+06
Name: 2018-12-31 00:00:00, dtype: float64

Vemos que para extraer una columna solo necesitamos pasar su nombre entre corchetes pero para un índice usamos el método loc. Pandas tiene diversas formas de extraer la información en función de como queramos hacerlo. Así el método loc extrae en base a la etiqueta del índice o columna, y el método iloc lo hace en función del numero ordinal que ocupa dicho índice o columna.

In [29]:
san.iloc[0]
Out[29]:
Open         1.324890e+01
High         1.333020e+01
Low          1.309530e+01
Close        1.317660e+01
Adj Close    5.868722e+00
Volume       1.039981e+08
Name: 2008-01-02 00:00:00, dtype: float64

Aunque para hacer una extracción por número de orden de varias filas no necesitamos usar iloc podemos hacerlo directamente.

In [57]:
san[100:106]
Out[57]:
OpenHighLowCloseAdj CloseVolume
Date
2008-05-2612.038712.038711.840011.91225.49111257912101
2008-05-2711.912211.993511.785811.81295.44533953285353
2008-05-2811.812912.101911.812911.96645.51609951351373
2008-05-2911.966412.120011.821911.96645.51609943426708
2008-05-3011.966412.120011.966412.09295.57440940694225
2008-06-0212.092912.092911.767711.82195.44948945788834

Si es útil cuando queremos hacer una extracción tanto de filas como de columnas.

In [31]:
san.iloc[:2,:2]
Out[31]:
OpenHigh
Date
2008-01-0213.248913.3302
2008-01-0313.140513.2037

Ahora veamos el cierre ajustado y el volumen desde el 1 al 10 de noviembre de 2015.

In [35]:
san.loc['2015-11-01':'2015-11-10', 'Adj Close':'Volume'] 
Out[35]:
Adj CloseVolume
Date
2015-11-024.37404057910508
2015-11-034.416542148301916
2015-11-044.483690105920463
2015-11-054.42589287484985
2015-11-064.50918486906806
2015-11-094.41399271676192
2015-11-104.39868739123436

Si tenemos en cuenta que lo se devuelve siempre es un dataframe o una serie, podemos encadenar varias formas de hacer extracciones.

Así por ejemplo podemos tomar primero 3 columnas por su nombre y después las diez últimas filas. En este caso no necesitamos usar iloc.

In [33]:
san[['Close', 'Low', 'Volume']][-10:]
Out[33]:
CloseLowVolume
Date
2018-12-144.05953.975565754616
2018-12-174.02704.025039823249
2018-12-183.99003.990044570803
2018-12-194.04154.008052444592
2018-12-203.97803.946558014374
2018-12-213.93053.8815212350900
2018-12-243.89153.874515720836
2018-12-273.86203.800051788055
2018-12-283.94503.863035454221
2018-12-313.97303.906516730575

O tomar dos columnas y usar el método head(). Por ejemplo para tomar los 5 primeros mínimos y máximos

In [15]:
san[['High','Low']].head()
Out[15]:
HighLow
Date
2008-01-0213.330213.0953
2008-01-0313.203712.9689
2008-01-0413.086312.7070
2008-01-0712.905712.6889
2008-01-0812.806312.4993

Además loc cuando lo usamos sobre un índice de fechas (datetime) permite extraer usando periodos. Así si queremos extraer los datos de mayo de 2018 podemos hacer lo siguiente.

In [43]:
san.loc['2018-05']
Out[43]:
OpenHighLowCloseAdj CloseVolume
Date
2018-05-025.3595.4155.35705.39805.202323320982382
2018-05-035.3905.4165.31505.33605.142571260700006
2018-05-045.3285.3795.30105.37605.18112134640717
2018-05-075.3875.4055.35605.38005.18497794307179
2018-05-085.3785.4235.32505.38405.18883168881042
2018-05-095.3905.4495.38505.43505.23798364844626
2018-05-105.4565.4865.41205.48605.28713437895987
2018-05-115.4905.5285.47405.51805.31797445371092
2018-05-145.5145.5375.47605.50605.30640932216377
2018-05-155.4915.5265.43205.46705.26882339260950
2018-05-165.4375.4605.31205.32905.13582554683384
2018-05-175.3385.3635.30705.35205.15799143080699
2018-05-185.3365.3435.18805.20605.01728390424536
2018-05-215.2425.2475.15605.16904.98162529991460
2018-05-225.1945.2875.16605.25805.06739935987383
2018-05-235.2495.2495.12505.16004.97295144558462
2018-05-245.1705.1935.09605.12604.94018441132419
2018-05-255.1505.1504.89654.99004.80911491746984
2018-05-285.0515.0694.87004.88804.71081254201863
2018-05-294.7804.7804.55704.62254.454936149494992
2018-05-304.6784.7384.57404.67404.504569106712823
2018-05-314.7394.8094.53854.60004.433251110356744

Existe otro método que es mas específico como es xs, aunque es más utilizado para los dataframe multi índices. Los cuales dejaremos para mas adelante.

In [46]:
bbva.xs('2018-05', axis=0)
Out[46]:
OpenHighLowCloseAdj CloseVolume
Date
2018-05-026.7186.7826.7186.7656.44322413089722
2018-05-036.7606.7606.6386.6666.34893321502091
2018-05-046.6806.7526.6306.7426.42131915853284
2018-05-076.7706.8126.7536.7886.46513013997103
2018-05-086.7916.8176.7306.7566.43465214733147
2018-05-096.7686.8106.7356.8066.48227513014601
2018-05-106.8206.8526.7896.8526.52608611695143
2018-05-116.8526.8856.8386.8546.52799110440243
2018-05-146.8456.8866.7966.8336.50799011957980
2018-05-156.8286.8556.6696.7296.40893723286596
2018-05-166.7086.7436.5586.6206.30512126357238
2018-05-176.6406.7086.6106.7076.38798321490061
2018-05-186.7006.7066.5466.5806.26702432373083
2018-05-216.6106.6236.4896.5086.19844925198841
2018-05-226.5266.6496.5136.6156.30035916624150
2018-05-236.5726.5726.3926.4676.15939935696613
2018-05-246.4846.5246.3476.3866.08225220716064
2018-05-256.4086.4166.1246.2085.91271838816362
2018-05-286.3076.3386.0906.1345.84223825479382
2018-05-296.0706.0875.8505.8785.59841449828929
2018-05-305.9506.0225.8255.9165.63460738820861
2018-05-315.9816.0665.7835.8395.56126954235540

Pandas también permite hacer una extracción (slice) usando saltos, es decir tomando los valores de cada N filas. Por ejemplo podemos tomar cada 15 filas. En este caso después usamos tail() para limitar lo que se muestra a solo los 8 últimos seleccionados por facilitar la visualización.

In [50]:
san[::15].tail(8)
Out[50]:
OpenHighLowCloseAdj CloseVolume
Date
2018-07-274.77954.85204.76504.81804.64334951335277
2018-08-174.39554.39554.31104.33954.23938842357176
2018-09-074.24254.25504.14004.17804.08161453997610
2018-09-284.43354.43454.25204.33554.23548069807695
2018-10-194.11004.16854.02704.12504.06322092057905
2018-11-094.25454.26904.19554.23904.17551235140481
2018-11-304.22704.23004.15154.18504.12232138399674
2018-12-213.96003.99553.88153.93053.871633212350900

Esto puede ser utilizado incluso para invertir el orden del índice, usando un paso negativo.

In [53]:
bbva[::-1].head(6)
Out[53]:
OpenHighLowCloseAdj CloseVolume
Date
2018-12-314.63504.66354.62004.63554.5005266885185
2018-12-284.55854.64804.55004.64104.50586620924025
2018-12-274.60004.63804.47654.54504.41266232726196
2018-12-244.57454.59904.54254.55804.4252835652399
2018-12-214.63304.66004.53404.61854.48402296489872
2018-12-204.65004.71504.62304.65404.51848727462924

Indexación booleana

Otra forma de extraer datos es usando una condición y solo nos devolverá la filas que cumplan dicha condición. Así por ejemplo podemos extraer las filas en las que el cierre de Santander supero los 13 euros, comprobando que solo fueron dos.

In [61]:
san[san['Close']>13]
Out[61]:
OpenHighLowCloseAdj CloseVolume
Date
2008-01-0213.248913.330213.095313.17665.868722103998100
2008-01-0313.140513.203712.968913.05025.812426113222703

La condición puede ser tan compleja como queramos, pudiendo usar los operadores lógicos para encadenar distintos condicionales.

In [73]:
bbva[(bbva.Open>15) & (bbva.Volume>10**8)]
Out[73]:
OpenHighLowCloseAdj CloseVolume
Date
2008-01-0415.794615.794615.323915.46808.089549138136794
2008-01-0915.237415.237414.997215.04527.868430107909320

Vemos además como en el ejemplo anterior hemos llamado las columnas Open y Volume sin usar los corchetes ni las comillas. Y es que si el nombre de la columna es una sola palabra (no contiene espacios) podemos llamarla usando solo un punto, como si de una propiedad del objeto dataframe se tratara.

Esto mismo también es posible hacerlo usando el método query(). Obtendremos exactamente el mismo resultado, solo que usando una expresión contenida en una cadena de texto para realizar la búsqueda.

In [76]:
bbva.query('Open>15 and Volume>10**8')
Out[76]:
OpenHighLowCloseAdj CloseVolume
Date
2008-01-0415.794615.794615.323915.46808.089549138136794
2008-01-0915.237415.237414.997215.04527.868430107909320

Dado que los dataframes san y bbva tienen el mismo tamaño (de hecho el mismo índice por cotizar en el mismo mercado), podemos usar una condición aplicada Santander para extraer valores del BBVA. Así por ejemplo podemos ver como cotizaba BBVA cuando Santander superó los 13 euros en su precio de cierre.

In [82]:
bbva[san.Close>13]
Out[82]:
OpenHighLowCloseAdj CloseVolume
Date
2008-01-0216.102116.159715.852315.92918.33069898910228
2008-01-0315.929115.929115.698615.79468.26035890699092

Si quieres ampliar información la propia documentación oficial de Pandas explica muy detalladamente estos métodos de extracción y algún otro.

In [ ]:
 

Puedes probar y modificar el notebook usado en este artículo, descargándolo desde su enlace en Github.