Una serie de tiempo es una secuencia de observaciones de momentos en el tiempo. La secuencia de datos está espaciada uniformemente a una frecuencia específica, como cada hora, o espaciada esporádicamente en el caso de un registro de llamadas telefónicas.
Es necesario tener un conocimiento experto de los datos de series temporales y cómo manipularlos para invertir y trading investigar. Este tutorial analizará los datos bursátiles mediante el análisis de series temporales con Python y Pandas. Todo el código y los datos asociados se pueden encontrar en el Analizando Alfa Github.
Comprensión de fechas y horas y deltas de tiempo
Es fundamental comprender la diferencia entre un momento, una duración y un período de tiempo antes de que podamos comprender por completo el análisis de series temporales en Python. Tipo Descripción Ejemplos Fecha (Momento) Día del año 2019-09-30, 30 de septiembre de 2019 hora6 horas, 6,5 minutos, 6,09 segundos, 6 milisegundos Fechahora (Momento)Combinación de fecha y hora2019-09-30 06:00:00, 30 de septiembre de 2019 a las 6:00DuraciónDiferencia entre dos momentos2 días, 4 horas, 10 segundosPeríodoAgrupación de tiempo2019Q3, enero
Módulo de fecha y hora de Python
fecha y hora proporciona clases para permitir la manipulación de la fecha y la hora de formas simples y complejas.
Creando Momentos
fechas, fechahorasy veces son cada una una clase separada, y podemos crearlos en una variedad de maneras, incluso directamente y a través de analizando cadenas.
import datetime
date = datetime.date(2019,9,30)
datetime1 = datetime.datetime(2019,9,30,6,30,9,123456)
datetime2_string = "10 03 2019 13:37:00"
datetime2 = datetime.datetime.strptime(datetime2_string,
'%m %d %Y %X')
datetime3_string = "Thursday, October 03, 19 1:37 PM"
datetime3 = datetime.datetime.strptime(datetime3_string,
'%A, %B %d, %y %I:%M %p')
time = datetime.time(6,30,9,123456)
now = datetime.datetime.today()
today = datetime.date.today()
print(type(date))
print(date)
print(type(datetime1))
print(datetime1)
print(datetime2)
print(datetime3)
print(type(time))
print(time)
print(now)
print(today)
<class 'datetime.date'>
2019-09-30
<class 'datetime.datetime'>
2019-09-30 06:30:09.123456
2019-10-03 13:37:00
2019-10-03 13:37:00
<class 'datetime.time'>
06:30:09.123456
2019-10-06 22:54:04.786039
2019-10-06
Creación de duraciones
timedeltas representan duraciones en el tiempo. Se pueden sumar o restar de momentos en el tiempo.
from datetime import timedelta
daysdelta = timedelta(days=5)
alldelta = timedelta(days=1, seconds=2,
microseconds=3,
milliseconds=4,
minutes=5,
hours=6,
weeks=7)
future = now + daysdelta
past = now - alldelta
print(type(future))
print(future)
print(type(past))
print(past)
<class 'datetime.datetime'>
2019-10-12 12:43:26.337336
<class 'datetime.datetime'>
2019-08-18 06:38:24.333333
Acceso a los atributos de fecha y hora
Los atributos de clase y objeto pueden ayudarnos a aislar la información que queremos ver. He enumerado los más comunes, pero puede encontrar la lista exhaustiva en el documentación del módulo de fecha y hora.Class / ObjectAttributeDescriptionShared Class Attributesclass.minPrimera fecha representable, datetime, timeclass.maxÚltima fecha representable, datetime, timeclass.resoluciónLa diferencia más pequeña entre dos fechas, datetimes u timesDate / Datetimeobject.yearReturns yearobject.monthReturns month of year (1 – 12)object .dayDevuelve el día del mes (1-32)Hora / Fechahoraobjeto.horaDevuelve la hora (0-23)objeto.minutoDevuelve el minuto (0-59)objeto.segundoDevuelve el segundo (0-59)
print(datetime.datetime.min)
print(datetime.datetime.max)
print(datetime.datetime.resolution)
print(datetime1.year)
print(datetime1.month)
print(datetime1.day)
print(datetime1.hour)
print(datetime1.minute)
print(datetime1.second)
print(datetime1.microsecond)
0001-01-01 00:00:00
9999-12-31 23:59:59.999999
0:00:00.000001
2019
9
30
6
30
9
123456
Serie temporal en pandas: momentos en el tiempo
Pandas fue desarrollado en el fondo de cobertura AQR por Wes McKinney para permitir un análisis rápido de datos financieros. Pandas es una extensión de NumPy que admite operaciones vectorizadas que permiten la manipulación y el análisis rápidos de datos de series temporales.
Marcas de tiempo: momentos en el tiempo
marca de tiempo extiende datetime64 de NumPy y se usa para representar datos de fecha y hora en Pandas. Pandas no requiere la fecha y hora de la biblioteca estándar de Python. Vamos a crear una marca de tiempo ahora usando to_datetime y pase los datos del ejemplo anterior.
import pandas as pd
print(type(pd.to_datetime('2019-09-30')))
print(pd.to_datetime('2019-09-30'))
print(pd.to_datetime('September 30th, 2019'))
print(pd.to_datetime('6:00:00'))
print(pd.to_datetime('2019-09-30 06:30:06'))
print(pd.to_datetime('September 30th, 2019 06:09.0006'))
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
2019-09-30 00:00:00
2019-09-30 00:00:00
2019-10-06 06:00:00
2019-09-30 06:30:06
2019-09-30 06:09:00
Timedeltas en Pandas: Duraciones de tiempo
Timedelta se utiliza para representar duraciones de tiempo internamente en Pandas.
timestamp1 = pd.to_datetime('September 30th, 2019 06:09.0006')
timestamp2 = pd.to_datetime('October 2nd, 2019 06:09.0006')
delta = timestamp2 - timestamp1
print(type(timestamp1))
print(type(delta))
print(delta)
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
<class 'pandas._libs.tslibs.timedeltas.Timedelta'>
2 days 00:00:00
Creación de una serie temporal en Pandas
Obtengamos el historial de acciones de Apple proporcionado por un Zona de pruebas para desarrolladores de Intrinio.
import pandas as pd
import urllib
url = "https://raw.githubusercontent.com/leosmigel/analyzingalpha/master/ ...
time-series-analysis-with-python/apple_price_history.csv"
with urllib.request.urlopen(url) as f:
apple_price_history = pd.read_csv(f)
apple_price_history[['open', 'high', 'low', 'close', 'volume']].head()
open high low close volume
0 28.75 28.87 28.75 28.75 2093900
1 27.38 27.38 27.25 27.25 785200
2 25.37 25.37 25.25 25.25 472000
3 25.87 26.00 25.87 25.87 385900
4 26.63 26.75 26.63 26.63 327900
Revisemos los tipos de datos o tipos de datos del marco de datos para ver si tenemos información de fecha y hora.
apple_price_history.dtypes
id int64
date object
open float64
high float64
low float64
close float64
volume int64
adj_open float64
adj_high float64
adj_low float64
adj_close float64
adj_volume int64
intraperiod bool
frequency object
security_id int64
dtype: object
Observe cómo la columna de fecha que contiene nuestra información de fecha es un objeto pandas. Podríamos haberle dicho a los pandas que parse_dates y leyeran en nuestra columna como una fecha, pero también podemos ajustarlo después del hecho.
Cambiemos el índice de rango de nuestro marco de datos a un índice de fecha y hora. Y por si acaso, leamos los datos con un DatetimeIndex de read_csv.
apple_price_history['date'] = apple_price_history['date'].astype(np.datetime64)
apple_price_history.dtypes
id int64
date datetime64[ns]
open float64
high float64
low float64
close float64
volume int64
adj_open float64
adj_high float64
adj_low float64
adj_close float64
adj_volume int64
intraperiod bool
frequency object
security_id int64
dtype: object
apple_price_history.set_index('date', inplace=True)
print(apple_price_history.index.dtype)
print(apple_price_history[['open', 'high', 'low', 'close']].head())
apple_price_history.index[0:10]
datetime64[ns]
open high low close
date
1980-12-12 28.75 28.87 28.75 28.75
1980-12-15 27.38 27.38 27.25 27.25
1980-12-16 25.37 25.37 25.25 25.25
1980-12-17 25.87 26.00 25.87 25.87
1980-12-18 26.63 26.75 26.63 26.63
DatetimeIndex(['1980-12-12', '1980-12-15', '1980-12-16', '1980-12-17',
'1980-12-18', '1980-12-19', '1980-12-22', '1980-12-23',
'1980-12-24', '1980-12-26'],
dtype='datetime64[ns]', name='date', freq=None)
import numpy as np
import urllib.request
names = ['open', 'high', 'low', 'close', 'volume']
url = 'https://raw.githubusercontent.com/leosmigel/analyzingalpha/master/ ...
time-series-analysis-with-python/apple_price_history.csv'
with urllib.request.urlopen(url) as f:
apple_price_history = pd.read_csv(f,
parse_dates=['date'],
index_col='date',
usecols=['date',
'adj_open',
'adj_high',
'adj_low',
'adj_close',
'adj_volume'])
apple_price_history.columns = names
print(apple_price_history.dtypes)
print(apple_price_history.index[:5])
print(apple_price_history.head())
open float64
high float64
low float64
close float64
volume int64
dtype: object
DatetimeIndex(['1980-12-12', '1980-12-15', '1980-12-16', '1980-12-17',
'1980-12-18'],
dtype='datetime64[ns]', name='date', freq=None)
open high low close volume
date
1980-12-12 0.410073 0.411785 0.410073 0.410073 117258400
1980-12-15 0.390532 0.390532 0.388678 0.388678 43971200
1980-12-16 0.361863 0.361863 0.360151 0.360151 26432000
1980-12-17 0.368995 0.370849 0.368995 0.368995 21610400
1980-12-18 0.379835 0.381546 0.379835 0.379835 18362400
Adición de fechas y horas desde cadenas
Con frecuencia, las fechas estarán en un formato que no podemos leer. Podemos usar dt.strftime para convertir la cadena en una fecha. Nosotros usamos strptime al crear el conjunto de datos S&P 500.
sp500.loc[:,’date’].apply(lambda x: fechahora.strptime(x,’%Y-%m-%d’))
Selección de series temporales
Ahora podemos seleccionar y dividir fechas fácilmente usando el índice con loc.
Selección de fecha y hora por día, mes o año
apple_price_history.index
print(apple_price_history.loc['2018'].head())
print(apple_price_history.loc['2018-06'].head())
print(apple_price_history.loc['2018-06-01': '2018-06-05'])
apple_price_history.loc['2018-6-1']
open high low close volume
date
2018-01-02 165.657452 167.740826 164.781266 167.701884 25555934
2018-01-03 167.964740 169.931290 167.409823 167.672678 29517899
2018-01-04 167.974475 168.879867 167.526647 168.451510 22434597
2018-01-05 168.850661 170.729592 168.470981 170.369382 23660018
2018-01-08 169.736582 170.963241 169.327695 169.736582 20567766
open high low close volume
date
2018-06-01 184.471622 186.697946 184.234938 186.678320 23442510
2018-06-04 188.047203 189.798784 187.767539 188.238552 26266174
2018-06-05 189.450430 190.309049 188.758629 189.690843 21565963
2018-06-06 190.004852 190.446427 188.326867 190.348300 20933619
2018-06-07 190.505304 190.564181 188.734097 189.838035 21347180
open high low close volume
date
2018-06-01 184.471622 186.697946 184.234938 186.678320 23442510
2018-06-04 188.047203 189.798784 187.767539 188.238552 26266174
2018-06-05 189.450430 190.309049 188.758629 189.690843 21565963
open 1.844716e+02
high 1.866979e+02
low 1.842349e+02
close 1.866783e+02
volume 2.344251e+07
Name: 2018-06-01 00:00:00, dtype: float64
Uso del descriptor de acceso de fecha y hora
fecha y hora tiene múltiples propiedades de fecha y hora y los métodos se pueden usar en elementos de fecha y hora de la serie como se encuentra en el Documentación de la API de la serie.PropertyDescriptionSeries.dt.dateDevuelve una matriz numérica de objetos python datetime.date (es decir, la parte de la fecha de las marcas de tiempo sin información de zona horaria).Series.dt.timeDevuelve una matriz numérica de datetime.time.Series.dt.timetzDevuelve una matriz numérica de datetime.time también contiene información de la zona horaria.Series.dt.yearEl año de la fecha y hora.Series.dt.monthEl mes como enero = 1, diciembre = 12.Series.dt.dayLos días de la fecha y hora.Series.dt.hourLas horas de la fecha y hora. Series.dt.minuteLos minutos de la fechahora.Series.dt.segundoLos segundos de la fechahora.Series.dt.microsegundoLos microsegundos de la fechahora.Series.dt.nanosegundoLos nanosegundos de la fechahora.Series.dt.semanaEl ordinal de la semana del año .Series.dt.weekofyearEl ordinal de la semana del año.Series.dt.dayofweekEl día de la semana con lunes=0, domingo=6.Series.dt.weekdayEl día de la semana con lunes=0, domingo=6.Series. dt.dayofyearEl día ordinal del año.Serie.dt.trimestreEl trimestre de la fecha.Series.dt.is_month_startIndica si el r la fecha es el primer día del mes.Series.dt.is_month_endIndica si la fecha es el último día del mes.Series.dt.is_trimestre_startIndicador de si la fecha es el primer día de un trimestre.Series.dt.is_trimestre_endIndicador de si la fecha es el último día de un trimestre.Series.dt.is_year_startIndica si la fecha es el primer día de un año.Series.dt.is_year_endIndica si la fecha es el último día del año.Series.dt.is_leap_yearIndicador booleano si la fecha pertenece a un año bisiesto.Series.dt.daysinmonthEl número de días en el mes.Series.dt.days_in_monthEl número de días en el mes.Series.dt.tzReturn time zone, if any.Series.dt.freqMethodDescriptionSeries.dt. to_period(self, *args, **kwargs)Transmitir a PeriodArray/Index a una frecuencia particular.Series.dt.to_pydatetime(self)Devolver los datos como una matriz de objetos nativos de fecha y hora de Python.Series.dt.tz_localize(self, * args, **kwargs) Localice tz-naive Datetime Array/Index a tz-aware Datetime Array/Index.Series.d t.tz_convert(self, *args, **kwargs)Convertir tz-aware Datetime Array/Index de una zona horaria a otra.Series.dt.normalize(self, *args, **kwargs)Convertir horas a medianoche.Series. dt.strftime(self, *args, **kwargs)Convertir a índice usando el formato de fecha especificado.Series.dt.round(self, *args, **kwargs)Realizar una operación de redondeo en los datos a la frecuencia especificada.Series.dt. floor(self, *args, **kwargs) Realiza la operación de piso en los datos a la freq.Series.dt.ceil(self, *args, **kwargs) Realiza la operación de ceil en los datos a la freq.Series especificada. dt.month_name(self, *args, **kwargs) Devuelve los nombres de los meses de DateTimeIndex con la configuración regional especificada.Series.dt.day_name(self, *args, **kwargs) Devuelve los nombres de los días de DateTimeIndex con la configuración regional especificada.
PeriodsPeriodSeries.dt.qyearSeries.dt.start_timeSeries.dt.end_time
dates = ['2019-01-01', '2019-04-02', '2019-07-03']
df = pd.Series(dates, dtype='datetime64[ns]')
print(df.dt.quarter)
print(df.dt.day_name())
DatetimeIndex incluye la mayoría de las mismas propiedades y métodos que dt.accessor.
print(apple_price_history.index.quarter)
apple_price_history.index.day_name()
0 1
1 2
2 3
dtype: int64
0 Tuesday
1 Tuesday
2 Wednesday
dtype: object
Int64Index([4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
...
3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
dtype='int64', name='date', length=9789)
Index(['Friday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
'Monday', 'Tuesday', 'Wednesday', 'Friday',
...
'Wednesday', 'Thursday', 'Friday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Monday', 'Tuesday'],
dtype='object', name='date', length=9789)
Selección de frecuencia
Las series de tiempo se pueden asociar con una frecuencia en Pandas cuando están espaciadas uniformemente.
rango de fechas es una función que nos permite crear una secuencia de fechas espaciadas uniformemente.
dates = pd.date_range('2019-01-01', '2019-12-31', freq='D')
dates
DatetimeIndex(['2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04',
'2019-01-05', '2019-01-06', '2019-01-07', '2019-01-08',
'2019-01-09', '2019-01-10',
...
'2019-12-22', '2019-12-23', '2019-12-24', '2019-12-25',
'2019-12-26', '2019-12-27', '2019-12-28', '2019-12-29',
'2019-12-30', '2019-12-31'],
dtype='datetime64[ns]', length=365, freq='D')
En lugar de especificar una fecha de inicio o finalización, podemos sustituir un período y ajustar la frecuencia.
dates = pd.date_range('2019-01-01', periods=6, freq='M')
print(dates)
hours = pd.date_range('2019-01-01', periods=24, freq='H')
print(hours)
DatetimeIndex(['2019-01-31', '2019-02-28', '2019-03-31', '2019-04-30',
'2019-05-31', '2019-06-30'],
dtype='datetime64[ns]', freq='M')
DatetimeIndex(['2019-01-01 00:00:00', '2019-01-01 01:00:00',
'2019-01-01 02:00:00', '2019-01-01 03:00:00',
'2019-01-01 04:00:00', '2019-01-01 05:00:00',
'2019-01-01 06:00:00', '2019-01-01 07:00:00',
'2019-01-01 08:00:00', '2019-01-01 09:00:00',
'2019-01-01 10:00:00', '2019-01-01 11:00:00',
'2019-01-01 12:00:00', '2019-01-01 13:00:00',
'2019-01-01 14:00:00', '2019-01-01 15:00:00',
'2019-01-01 16:00:00', '2019-01-01 17:00:00',
'2019-01-01 18:00:00', '2019-01-01 19:00:00',
'2019-01-01 20:00:00', '2019-01-01 21:00:00',
'2019-01-01 22:00:00', '2019-01-01 23:00:00'],
dtype='datetime64[ns]', freq='H')
asfreq devuelve un marco de datos o una serie con una nueva frecuencia especificada. Se agregarán nuevas filas para los momentos que faltan en los datos y se llenan con NaN o mediante un método que especifiquemos. A menudo necesitamos proporcionar un alias de compensación para obtener la frecuencia de tiempo deseada.
Alias compensados
AliasDescripciónBFrecuencia de días hábilesCFrecuencia de días hábiles personalizadaDFrecuencia de días calendarioWFrecuencia semanalMFrecuencia de fin de mesSMFrecuencia de fin de mes hábil (15 y fin de mes)BMFrecuencia de fin de mes hábilCBMFrecuencia de fin de mes hábil personalizadaMSFrecuencia de inicio de mesSMSFrecuencia de inicio de semestre (1 y 15)BMSFrecuencia de inicio de mes hábilCBMSFrecuencia de inicio de mes hábilQFinal de trimestre frecuenciaBQfrecuencia de fin de trimestre comercialQSfrecuencia de inicio de trimestreBQSfrecuencia de inicio de trimestre comercialA, Yfrecuencia de final de añoBA, BYfrecuencia de final de año comercialAS,YSfrecuencia de inicio de añoBAS,BYSfrecuencia de inicio de año comercialBHfrecuencia de hora comercialHfrecuencia por horaT,minfrecuencia por minutoSfrecuencia por segundoL,msmilisegundosU,us microsegundosNnanosegundos
print(apple_price_history.asfreq('BA').head())
apple_quarterly_history = apple_price_history.asfreq('BM')
print(type(apple_quarterly_history))
apple_quarterly_history.head()
open high low close volume
date
1980-12-31 0.488522 0.488522 0.486810 0.486810 8937600
1981-12-31 0.315649 0.317361 0.315649 0.315649 13664000
1982-12-31 0.427903 0.433180 0.426048 0.426048 12415200
1983-12-30 0.347742 0.356585 0.345888 0.347742 22965600
1984-12-31 0.415351 0.417205 0.415351 0.415351 51940000
<class 'pandas.core.frame.DataFrame'>
open high low close volume
date
1980-12-31 0.488522 0.488522 0.486810 0.486810 8937600.0
1981-01-30 0.406507 0.406507 0.402942 0.402942 11547200.0
1981-02-27 0.377981 0.381546 0.377981 0.377981 3690400.0
1981-03-31 0.353020 0.353020 0.349454 0.349454 3998400.0
1981-04-30 0.404796 0.408219 0.404796 0.404796 3152800.0
Datos de relleno
asfreq nos permite proporcionar un método de llenado para reemplazar los valores de NaN.
print(apple_price_history['close'].asfreq('H').head())
print(apple_price_history['close'].asfreq('H', method='ffill').head())
date
1980-12-12 00:00:00 0.410073
1980-12-12 01:00:00 NaN
1980-12-12 02:00:00 NaN
1980-12-12 03:00:00 NaN
1980-12-12 04:00:00 NaN
Freq: H, Name: close, dtype: float64
date
1980-12-12 00:00:00 0.410073
1980-12-12 01:00:00 0.410073
1980-12-12 02:00:00 0.410073
1980-12-12 03:00:00 0.410073
1980-12-12 04:00:00 0.410073
Freq: H, Name: close, dtype: float64
Remuestreo: Upsampling y Downsampling
volver a muestrear devuelve un objeto de remuestreo, muy similar a un objeto groupby, para el cual ejecutar varios cálculos.
A menudo necesitamos reducir (disminuir el muestreo) o aumentar (aumentar el muestreo) la frecuencia de nuestros datos de series temporales. Si tenemos datos de ventas diarios o mensuales, puede ser útil reducir la muestra a datos trimestrales. Alternativamente, es posible que deseemos aumentar la muestra de nuestros datos para que coincidan con la frecuencia de otra serie que estamos usando para hacer predicciones. El sobremuestreo es menos común y requiere interpolación.
apple_quarterly_history = apple_price_history.resample('BM')
print(type(apple_quarterly_history))
apple_quarterly_history.agg({'high':'max', 'low':'min'})[:5]
pandas.core.resample.DatetimeIndexResampler
high low
date
1980-12-31 0.515337 0.360151
1981-01-30 0.495654 0.402942
1981-02-27 0.411785 0.338756
1981-03-31 0.385112 0.308375
1981-04-30 0.418917 0.345888
Ahora podemos usar todas las propiedades y métodos que descubrimos anteriormente.
print(apple_price_history.index.dayofweek)
print(apple_price_history.index.weekofyear)
print(apple_price_history.index.year)
print(apple_price_history.index.day_name())
Int64Index([4, 0, 1, 2, 3, 4, 0, 1, 2, 4,
...
2, 3, 4, 0, 1, 2, 3, 4, 0, 1],
dtype='int64', name='date', length=9789)
Int64Index([50, 51, 51, 51, 51, 51, 52, 52, 52, 52,
...
37, 37, 37, 38, 38, 38, 38, 38, 39, 39],
dtype='int64', name='date', length=9789)
Int64Index([1980, 1980, 1980, 1980, 1980, 1980, 1980, 1980, 1980, 1980,
...
2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019],
dtype='int64', name='date', length=9789)
Index(['Friday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
'Monday', 'Tuesday', 'Wednesday', 'Friday',
...
'Wednesday', 'Thursday', 'Friday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Monday', 'Tuesday'],
dtype='object', name='date', length=9789)
datetime = pd.to_datetime('2019-09-30 06:54:32.54321')
print(datetime.floor('H'))
print(datetime.ceil('T'))
print(datetime.round('S'))
print(datetime.to_period('W'))
print(datetime.to_period('Q'))
datetime.to_period('Q').end_time
2019-09-30 06:00:00
2019-09-30 06:55:00
2019-09-30 06:54:33
2019-09-30/2019-10-06
2019Q3
Timestamp('2019-09-30 23:59:59.999999999')
Ventanas móviles: suavizado y medias móviles
laminación nos permite dividir los datos en ventanas agregadas y aplicar una función como la media o la suma.
Un ejemplo típico de esto en trading está usando el de 50 días y el de 200 días [moving averages/moving-average) to enter and exit an asset.
Let’s calculate the these for Apple. Notice that we need 50 days of data before we can calculate the rolling mean.
Visualizing Time Series Data using Matplotlib
Matplotlib makes it easy to visualize our Pandas time series data. Seaborn adds additional options and helps us make our graphs look prettier. Let’s import matplotlib and seaborn to try out a few basic examples. This quick summary isn’t an in-depth guide on Python Visualization.
Line Plot
lineplot draws a standard line plot. It works similar to dataframe.plot that we’ve been using above.
import matplotlib.pyplot as plt
import seaborn as sns
fig, ax = plt.subplots(figsize=(32,18))
sns.lineplot(x=apple_price_history.index, y='close', data=apple_price_history,
ax=ax).set_title("Apple Stock Price History")
Boxplots
boxplot enables us to group and understand distributions in our data. It’s often beneficial for seasonal data.
apple_price_recent_history = apple_price_history[-800:].copy() apple_price_recent_history['quarter'] = apple_price_recent_history.index.year.astype(str)... + apple_price_recent_history.index. Quarter.astype(str) sns.set(rc={'figure.figsize':(18, 9)}) sns.boxplot(datos =apple_price_recent_history, x='trimestre', y='cerrar')
Análisis de datos de series temporales en Pandas
Los métodos de análisis de series de tiempo se pueden dividir en dos clases:
Los métodos de dominio de frecuencia analizan cuánto cambia una señal en una banda de frecuencia, como las últimas 100 muestras. Los métodos en el dominio del tiempo analizan cuánto cambia una señal durante un período de tiempo específico, como los 100 segundos anteriores.
Serie temporal Tendencia, estacionalidad y ciclicidad
Los datos de series de tiempo pueden ser descompuesto en cuatro componentes:
No todas las series temporales tienen tendencia, estacionalidad o carácter cíclico; además, debe haber suficientes datos para respaldar que existe estacionalidad, ciclicidad o una tendencia y que lo que está viendo no es solo un patrón aleatorio.
Los datos de series temporales a menudo mostrarán una variabilidad gradual además de una variabilidad de frecuencia más alta, como la estacionalidad y el ruido. Una manera fácil de visualizar estas tendencias es con medios móviles en diferentes escalas de tiempo. Importemos los datos de ventas de Apple para revisar la estacionalidad y la tendencia.
Tendencia
Tendencias ocurren cuando hay una pendiente creciente o decreciente en la serie de tiempo. El crecimiento de las ventas de Amazon sería un ejemplo de una tendencia al alza. Además, las tendencias no tienen que ser lineales. Las tendencias pueden ser deterministas y son una función del tiempo, o estocásticas donde la tendencia es aleatoria.
estacionalidad
estacionalidad Ocurre cuando hay un patrón repetitivo distinto, picos y valles, observados a intervalos regulares dentro de un año. El pico de ventas de Apple en el cuarto trimestre sería un ejemplo de estacionalidad en las cifras de ingresos de Amazon.
ciclicidad
ciclicidad Ocurre cuando hay un patrón repetitivo distinto, picos y valles, observados a intervalos irregulares. El ciclo económico exhibe carácter cíclico.
Analicemos el historial de ingresos de Apple y veamos si podemos descomponerlo visual y programáticamente.
import urllib
import pandas as pd
from scipy import stats
url = 'https://raw.githubusercontent.com/leosmigel/analyzingalpha/master/ ...
time-series-analysis-with-python/apple_revenue_history.csv'
with urllib.request.urlopen(url) as f:
apple_revenue_history = pd.read_csv(f, index_col=0)
apple_revenue_history['quarter'] = apple_revenue_history['fiscal_year'].apply(str) \
+ apple_revenue_history['fiscal_period'].str.upper()
slope, intercept, r_value, p_value, std_err = stats.linregress(apple_revenue_history.index,
apple_revenue_history['value'])
apple_revenue_history['line'] = slope * apple_revenue_history.index + intercept
Gráfico de tendencia de serie temporal con línea de tendencia
fig = plt.figure(figsize=(32,18))
ax1 = fig.add_subplot(1,1,1)
apple_revenue_history.plot(y='value', x='quarter', title='Apple Quarterly Revenue 2010-2018', ax=ax1)
apple_revenue_history.plot(y='line', x='quarter', title='Apple Quarterly Revenue 2010-2018', ax=ax1)
Gráfico apilado de series temporales para análisis de ciclos
fig = plt.figure(figsize=(32,18))
ax1 = fig.add_subplot(1,1,1)
legend = []
yticks = np.linspace(apple_revenue_history['value'].min(), apple_revenue_history['value'].max(), 10)
for year in apple_revenue_history['fiscal_year'].unique():
apple_revenue_year = apple_revenue_history[apple_revenue_history['fiscal_year'] == year]
legend.append(year)
apple_revenue_year.plot(y='value', x='fiscal_period', title='Apple Quarterly Revenue 2010-2018',
ax=ax1,yticks=yticks, sharex=True, sharey=True)
ax1.legend(legend)
Descomposición de datos de series temporales con StatsModel
modelo de estadísticas nos permite descomponer estadísticamente una serie de tiempo en sus componentes.
from statsmodels.tsa.seasonal import seasonal_decompose
from dateutil.parser import parse
apple_revenue_history['date'] = pd.to_datetime(apple_revenue_history['quarter']).dt.to_period('Q')
apple_revenue_history.set_index('date', inplace=True)
apple_revenue_history.index = apple_revenue_history.index.to_timestamp(freq='Q')
result_add = seasonal_decompose(apple_revenue_history['value'])
plt.rcParams.update({'figure.figsize': (32,18)})
result_add.plot().suptitle('Decompose (Additive)', fontsize=18)
plt.show()
Estacionariedad de series de tiempo
Las series temporales son diferentes de los problemas de modelado predictivo de clasificación y regresión más tradicionales. Los datos de series temporales están ordenados y deben ser estacionarios para obtener estadísticas de resumen significativas.
Estacionariedad es una suposición que subyace en muchos procedimientos estadísticos utilizados en el análisis de series de tiempo, y los datos no estacionarios a menudo se transforman en datos estacionarios.
La estacionariedad a veces se clasifica en lo siguiente:
- Proceso/Modelo Estacionario: Serie estacionaria de observaciones.
- Tendencia estacionaria: no muestra una tendencia.
- Estacionario Estacional: No exhibe estacionalidad.
- Estrictamente Estacionario: Definición matemática de un proceso estacionario.
En una serie de tiempo estacionaria, la media y la desviación estándar de una serie de tiempo es constante. Además, no hay estacionalidad, ciclicidad u otra estructura dependiente del tiempo. A menudo, es más fácil comprender si una serie temporal es estacionaria observar primero cómo se puede violar la estacionariedad.
# Stationary
vol = .002
df1 = pd.DataFrame(np.random.normal(size=200) * vol)
df1.plot(title='Stationary')
Series temporales estacionarias
df2 = pd.DataFrame(np.random.random(size=200) * vol).cumsum()
df2.plot(title='Not Stationarty: Mean Not Constant')
No estacionario: media no constante
df3 = pd.DataFrame(np.random.normal(size=200) * vol * np.logspace(1,2,num=200, dtype=int))
df3.plot(title='Not Stationary: Volatility Not Constant')
No Estacionario: Volatilidad No Constante
df4 = pd.DataFrame(np.random.normal(size=200) * vol)
df4['cyclical'] = df4.index.values % 20
df4[0] = df4[0] + df4['cyclical']
df4[0].plot(title='Not Stationary: Cyclcial')
No Estacionario: Cíclico
Cómo probar la estacionariedad
Podemos probar la estacionariedad inspeccionando visualmente los gráficos anteriores, como hicimos antes; dividiendo los gráficos en varias secciones y observando las estadísticas de resumen, como la media, la varianza y la correlación; o podemos utilizar métodos más avanzados como la prueba Dickey-Fuller aumentada.
El Dickey-Fuller aumentado prueba que no hay raíz unitaria. Si la serie temporal tiene una raíz unitaria, tiene una estructura dependiente del tiempo, lo que significa que la serie temporal no es estacionaria.
Cuanto más negativa sea esta estadística, más probable es que tenga una serie temporal estacionaria. En general, si el valor p > 0,05 los datos tienen raíz unitaria y no son estacionarios. Usemos statsmodel para examinar esto.
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import adfuller
df1 = pd.DataFrame(np.random.normal(size=200))
result = adfuller(df1[0].values, autolag='AIC')
print(f'ADF Statistic: {result[0]:.2f}')
print(f'p-value: {result[1]:.2f}')
for key, value in result[4].items():
print('Critial Values:')
ADF Statistic: -11.14
p-value: 0.00
Critial Values:
1%, -3.46
Critial Values:
5%, -2.88
Critial Values:
10%, -2.57
ADF Statistic: -0.81
p-value: 0.81
Critial Values:
1%, -3.47
Critial Values:
5%, -2.88
Critial Values:
10%, -2.58
Al ejecutar los ejemplos, se imprimen los valores estadísticos de prueba de 0,00 (estacionario) y 0,81 (no estacionario), respectivamente.
Observe que podemos ver la probabilidad de que podamos rechazar que la serie de precios no sea estacionaria. En la primera serie, podemos decir que la serie de precios es estacionaria por encima del nivel de confianza del 1%. En la siguiente serie, no podemos rechazar que la serie de precios no es estacionaria; en otras palabras, esta no es una serie de precios estacionaria ya que ni siquiera alcanza el umbral del 10%.
Cómo manejar series temporales no estacionarias
Si hay una tendencia y estacionalidad claras en una serie de tiempo, modele estos componentes, elimínelos de las observaciones y luego entrene los modelos en los residuos.
Eliminar la tendencia de una serie temporal
Existen varios métodos para eliminar el componente de tendencia de una serie temporal.
- Reste la línea de mejor ajuste
- Restar usando una descomposición
- Restar usando un filtro
Línea de mejor ajuste con SciPy
Detrend de SciPy nos permite eliminar la tendencia restando la línea de mejor ajuste.
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
from scipy import signal
vol = 2
df = pd.DataFrame(np.random.random(size=200) * vol).cumsum()
df[0].plot(figsize=(32,18))
detrend = signal.detrend(df[0].values)
plt.plot(detrend)
Descomponer y extraer usando StatsModels
descomposición_estacional devuelve un objeto con los atributos de temporada, tendencia y residencia que podemos restar de los valores de nuestra serie.
from statsmodels.tsa.seasonal import seasonal_decompose
from dateutil.parser import parse
dates = pd.date_range('2019-01-01', periods=200, freq='D')
df = pd.DataFrame(np.random.random(200)).cumsum()
df.set_index(dates, inplace=True)
df[0].plot()
decompose = seasonal_decompose(df[0], model='additive', extrapolate_trend='freq')
df[0] = df[0] - decompose.trend
df[0].plot()