Análisis de series temporales con Python simplificado

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:

  • Métodos de dominio de frecuencia
  • Métodos en el dominio del tiempo
  • 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:

  • Tendencia
  • estacionalidad
  • ciclicidad
  • Ruido
  • 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()

    Deja un comentario