Análise Prática de Séries Temporais - Livro
Análise Prática de Séries Temporais - Livro
Figura 1-1. As tabelas atuariais de John Graunt foram um dos primeiros resultados do
pensamento no estilo de série temporal aplicado a questões médicas. Fonte: Wikipedia
(https://perma.cc/2FHJ-67SB).
1
Figura 1-2. Um registro inicial de ECG do artigo original de 1877 de Augustus D. Walder,
MD, “A Demonstration on Man of Electromotive Changes Accompanying the Heart’s Beat”
[Uma Demonstração Individual das Mudanças Eletromotivas que Acompanham as Batidas
do Coração, em tradução livre] (https://perma.cc/ZGB8-3C95). Os ECGs mais antigos eram
difíceis de construir e usar, por isso levaram mais algumas décadas para que se tornassem
uma ferramenta operacional para os clínicos gerais.
Figura 1-4. O governo federal dos EUA financia muitas agências governamentais e
organizações sem fins lucrativos que registram estatísticas vitais e elaboram indicadores
econômicos. Fonte: Agência Nacional de Pesquisa Econômica dos Estados Unidos (https://
www.nber.org/cycles/cyclesmain.html).
Figura 2-1. O UCI Machine Learning Repository inclui uma lista com explicações detalhadas
de conjuntos de dados de séries temporais.
3
Figura 2-2. O conjunto de dados de absenteísmo no trabalho [absenteeism at work data set]
é o primeiro na lista de conjuntos de dados de séries temporais no UCI Machine Learning
Repository.
Figura 2-4. Gráficos de um ator e uma atriz realizando movimentos de yoga repetidamente.
Plotamos duas séries temporais de amostra por ator. Não há rótulos de tempo explícitos no
eixo x. Em vez da unidade de tempo, o que importa é se os pontos de dados do eixo x estão
espaçados uniformemente, como são apresentados aqui.
## python
>>> YearJoined.groupby('memberId').count().
groupby('memberStats').count()
1000
## python
>>> emails[emails.EmailsOpened < 1]
Empty DataFrame
Columns: [EmailsOpened, member, week]
Index: []
## python
>>> emails[emails.member == 998]
EmailsOpened member week
25464 1 998 2017-12-04
25465 3 998 2017-12-11
25466 3 998 2017-12-18
25467 3 998 2018-01-01
25468 3 998 2018-01-08
25469 2 998 2018-01-15
25470 3 998 2018-01-22
25471 2 998 2018-01-29
25472 3 998 2018-02-05
25473 3 998 2018-02-12
25474 3 998 2018-02-19
25475 2 998 2018-02-26
25476 2 998 2018-03-05
## python
>>> (max(emails[emails.member == 998].week) -
min(emails[emails.member == 998].week)).days/7
25.0
## python
>>> complete_idx = pd.MultiIndex.from_product((set(emails.week),
set(emails.member)))
## python
>>> all_email = emails.set_index(['week', 'member']).
reindex(complete_idx, fill_value = 0).
reset_index()
>>> all_email.columns = ['week', 'member', 'EmailsOpened']
## python
>>> all_email[all_email.member == 998].sort_values('week')
week member EmailsOpened
2015-02-09 998 0
2015-02-16 998 0
2015-02-23 998 0
2015-03-02 998 0
2015-03-09 998 0
## python
>>> cutoff_dates = emails.groupby('member').week.
agg(['min', 'max']).reset_index)
>>> cutoff_dates = cutoff_dates.reset_index()
## python
>>>python
## for _, row in cutoff_dates.iterrows():
>>> for
member
_, row in=cutoff_dates.iterrows():
row['member']
>>> start_date
member = row['min']
row['member']
>>> end_date
start_date = row['max']
row['min']
>>> all_email.drop(
end_date = row['max']
all_email[all_email.member == member]
>>> all_email.drop(
[all_email.week < start_date].index,
all_email[all_email.member inplace=True)
== member]
>>> all_email.drop(all_email[all_email.member
[all_email.week < start_date].index, ==inplace=True)
member]
[all_email.week > end_date].index, inplace=True)
>>> all_email.drop(all_email[all_email.member == member]
[all_email.week > end_date].index, inplace=True)
## python
>>> donations.timestamp = pd.to_datetime(donations.timestamp)
>>> donations.set_index('timestamp', inplace = True)
>>> agg_don = donations.groupby('member').apply(
lambda df: df.amount.resample("W-MON").sum().dropna())
## python
>>> df = merged_df[merged_df.member == 998]
>>> df['target'] = df.amount.shift(1)
>>> df = df.fillna(0)
>>> df
## R
> require(zoo) ## O zoo fornece os recursos para séries temporais.
> require(data.table) ## O data.table é um dataframe de alto desempenho.
Rolling Joins
## R
> ## Temos um pequeno data.table das datas de doação.
> donations <- data.table(
> amt = c(99, 100, 5, 15, 11, 1200),
> dt = as.Date(c("2019-2-27", "2019-3-2", "2019-6-13",
> "2019-8-1", "2019-8-31", "2019-9-15"))
> )
## R
> publicity[donations, roll = TRUE]
identifier dt amt
1: q4q42 2019-02-27 99
2: q4q42 2019-03-02 100
3: 4299hj 2019-06-13 5
4: bbg2 2019-08-01 15
5: bbg2 2019-08-31 11
6: bbg2 2019-09-15 1200
## R
> rand.unemp[, impute.ff := na.locf(UNRATE, na.rm = FALSE)]
> bias.unemp[, impute.ff := na.locf(UNRATE, na.rm = FALSE)]
>
> ## Para plotar um gráfico de amostra que mostra as partes achatadas.
> unemp[350:400, plot (DATE, UNRATE,
col = 1, lwd = 2, type = 'b')]
> rand.unemp[350:400, lines(DATE, impute.ff,
col = 2, lwd = 2, lty = 2)]
> rand.unemp[350:400][rpt == TRUE, points(DATE, impute.ff,
col = 2, pch = 6, cex = 2)]
Figura 2-7. Plotando a taxa de desemprego real versus uma série que usou o foward fill. Esse
gráfico mostra que o forward fill não distorceu sistematicamente os dados.
## R
> ## média móvel sem lookahead
> rand.unemp[, impute.rm.nolookahead := rollapply(c(NA, NA, UNRATE), 3,
> function(x) {
> if (!is.na(x[3])) x[3] else mean(x, na.rm = TRUE)
> })]
> bias.unemp[, impute.rm.nolookahead := rollapply(c(NA, NA, UNRATE), 3,
> function(x) {
> if (!is.na(x[3])) x[3] else mean(x, na.rm = TRUE)
> })]
## R
> sort(rand.unemp[ , lapply(.SD, function(x) mean((x - unemp$UNRATE)^2,
> na.rm = TRUE)),
> .SDcols = c("impute.ff", "impute.rm.lookahead",
> "impute.rm.nolookahead", "impute.li",
> "impute.sp")])
impute.li impute.rm.lookahead impute.sp impute.ff impute.rm.nolookahead
0.0017 0.0019 0.0021 0.0056 0.0080
## R
> unemp[, mean(UNRATE), by = format(DATE, "%Y")]
format V1
1948 3.75
1949 6.05
1950 5.21
1951 3.28
1952 3.03
1953 2.93
1954 5.59
1955 4.37
1956 4.13
1957 4.30
## R
> all.dates <- seq(from = unemp$DATE[1], to = tail(unemp$DATE, 1),
> by = "months")
> rand.unemp = rand.unemp[J(all.dates), roll=0]
## R
> daily.unemployment = unemp[J(all.dates), roll = 31]
> daily.unemployment
DATE UNRATE
1948-01-01 3.4
1948-01-02 3.4
1948-01-03 3.4
1948-01-04 3.4
1948-01-05 3.4
## python
>>> air
Date Passengers
0 1949-01 112
1 1949-02 118
2 1949-03 132
3 1949-04 129
4 1949-05 121
5 1949-06 135
6 1949-07 148
7 1949-08 148
8 1949-09 136
9 1949-10 119
10 1949-11 104
## python
>>> air
Date Passengers Smooth.5 Smooth.9
0 1949-01 112 112.000000 112.000000
1 1949-02 118 116.000000 117.454545
2 1949-03 132 125.142857 130.558559
3 1949-04 129 127.200000 129.155716
4 1949-05 121 124.000000 121.815498
5 1949-06 135 129.587302 133.681562
6 1949-07 148 138.866142 146.568157
7 1949-08 148 143.450980 147.856816
8 1949-09 136 139.718200 137.185682
9 1949-10 119 129.348974 120.818568
10 1949-11 104 116.668295 105.681857
Figura 2-10. O
aumento da média
e da variância dos
dados são aparentes
em um gráfico de
dispersão, mas não
vemos uma tendência
sazonal óbvia.
>>> datetime.datetime.now()
datetime.datetime(2018, 5, 31, 10, 49, 59, 984947)
>>> # Como podemos ver, minha máquina não retorna UTC,
>>> # embora não haja fuso horário anexado.
>>> datetime.datetime.now(datetime.timezone.utc)
datetime.datetime(2018, 5, 31, 14, 51, 35, 601355,
tzinfo=datetime.timezone.utc)
## python
>>> western = pytz.timezone('US/Pacific')
>>> western.zone
'US/Pacific'
## python
>>> ## A API oferece suporte a duas maneiras de construir um horário
>>> ## consciente do fuso horário, seja por meio de 'localize' ou
>>> ## para converter um fuso horário de um local em outro.
>>> # Aqui, usei o localize
>>> loc_dt = western.localize(datetime.datetime(2018, 5, 15, 12, 34, 0))
datetime.datetime(2018, 5, 15, 12, 34,
tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
## python
>>> london_tz = pytz.timezone('Europe/London')
>>> london_dt = loc_dt.astimezone(london_tz)
>>> london_dt
datetime.datetime(2018, 5, 15, 20, 34,
tzinfo=<DstTzInfo 'Europe/London' BST+1:00:00 DST>)
>>> ## De acordo com a documentação do pytz, esse método leva aos resultados
>>> ## desejados em fusos horários sem horário de verão.
## python
## Dê uma olhada no pytz.common_timezones
>>> pytz.common_timezones
(long output...)
## Ou países específicos
>>> pytz.country_timezones('RU')
['Europe/Kaliningrad', 'Europe/Moscow', 'Europe/Simferopol',
'Europe/Volgograd', 'Europe/Kirov', 'Europe/Astrakhan',
'Europe/Saratov', 'Europe/Ulyanovsk', 'Europe/Samara',
'Asia/Yekaterinburg', 'Asia/Omsk', 'Asia/Novosibirsk',
'Asia/Barnaul', 'Asia/Tomsk', 'Asia/Novokuznetsk',
'Asia/Krasnoyarsk', 'Asia/Irkutsk', 'Asia/Chita',
'Asia/Yakutsk', 'Asia/Khandyga', 'Asia/Vladivostok',
'Asia/Ust-Nera', 'Asia/Magadan', 'Asia/Sakhalin',
'Asia/Srednekolymsk', 'Asia/Kamchatka', 'Asia/Anadyr']
>>>
>>> pytz.country_timezones('fr')
>>> ['Europe/Paris']
## R
> head(EuStockMarkets)
DAX SMI CAC FTSE
[1,] 1628.75 1678.1 1772.8 2443.6
[2,] 1613.63 1688.5 1750.5 2460.2
## R
> plot(EuStockMarkets)
Figura 3-1. Um gráfico simples dos dados das séries temporais.
## R
> class(EuStockMarkets)
[1] "mts" "ts" "matrix"
## R
> frequency(EuStockMarkets)
[1] 260
## R
> start(EuStockMarkets)
[1] 1991 130
> end(EuStockMarkets)
1] 1998 169
## R
> window(EuStockMarkets, start = 1997, end = 1998)
Time Series:
Start = c(1997, 1)
End = c(1998, 1)
Frequency = 260 DAX SMI CAC FTSE
1997.000 2844.09 3869.8 2289.6 4092.5
1997.004 2844.09 3869.8 2289.6 4092.5
1997.008 2844.09 3869.8 2303.8 4092.5
...
1997.988 4162.92 6115.1 2894.5 5168.3
1997.992 4055.35 5989.9 2822.9 5020.2
1997.996 4125.54 6049.3 2869.7 5018.2
1998.000 4132.79 6044.7 2858.1 5049.8
Figura 3-2. O histograma dos dados não transformados (parte superior) é bastante amplo
e não mostra uma distribuição normal. Isso é esperado, visto que os dados subjacentes
apresentam uma tendência. Diferenciamos os dados para remover a tendência e isso
transforma os dados até uma forma de distribuição mais normal (parte inferior).
## R
> plot( EuStockMarkets[, "SMI"], EuStockMarkets[, "DAX"])
> plot(diff(EuStockMarkets[, "SMI"]), diff(EuStockMarkets[, "DAX"]))
## R
> plot(lag(diff(EuStockMarkets[, "SMI"]), 1),
diff(EuStockMarkets[, "DAX"]))
Figura 3-4. A correlação entre as ações desaparece assim que inserimos um lag de tempo,
indicando que o SMI [Índice de Bolsa de Valores Suíço] não parece predizer o DAX [Índice
de Ações Alemão].
## R
> ## Calcula uma média móvel usando a base R.
> ## Função de filtro.
> x <- rnorm(n = 100, mean = 0, sd = 10) + 1:100
> mn <- function(n) rep(1/n, n)
## R
> ## Você também pode escrever funções customizadas
> require(zoo)
## R
> # expanding windows/janela de expansão.
> plot(x, type = 'l', lwd = 1)
> lines(cummax(x), col = 2, lwd = 3, lty = 2) # max
> lines(cumsum(x)/1:length(x), col = 3, lwd = 3, lty = 3) # mean
Figura 3-8. Uma janela de expansão “max” (traços longos) e uma janela de expansão “mean”
(traços curtos). O uso de uma janela de expansão significa que o max sempre reflete o máximo
global até aquele tempo, tornando-a uma função monotônica. Graças à extensa “memória”
## R
> plot(x, type = 'l', lwd = 1)
> lines(rollapply(zoo(x), seq_along(x), function(w) max(w),
> partial = TRUE, align = "right"),
> col = 2, lwd = 3, lty = 2)
> lines(rollapply(zoo(x), seq_along(x), function(w) mean(w),
> partial = TRUE, align = "right"),
> col = 3, lwd = 3, lty = 3)
## R
> x <- 1:100
> y <- sin(x × pi /3)
> plot(y, type = "b")
> acf(y)
## R
> cor(y, shift(y, 1), use = "pairwise.complete.obs")
[1] 0.5000015
> cor(y, shift(y, 2), use = "pairwise.complete.obs")
[1] -0.5003747
## R
> y1 <- sin(x × pi /3)
> plot(y1, type = "b")
> acf (y1)
> pacf(y1)
## R
> y <- y1 + y2
> plot(y, type = "b")
> acf (y)
> pacf(y)
## R
> noise1 <- rnorm(100, sd = 0.05)
> noise2 <- rnorm(100, sd = 0.05)
Figura 3-17. Algumas correlações espúrias podem parecer bastante convincentes. Esse gráfico
foi retirado do site de Tyler Vigen (https://perma.cc/6UYH-FPBX) de correlações espúrias
[Número de morte por afogamento em piscinas correlacionado com a aparição de
Nicolas Cage em filmes].
Figura 3-18. Esse gráfico de Gantt de uma amostra aleatória de dados pode nos dar uma
ideia da distribuição das faixas de períodos de tempo “ativos” para os usuários/doadores.
## R
> t(matrix(AirPassengers, nrow = 12, ncol = 12))
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
[1,] 112 118 132 129 121 135 148 148 136 119 104 118
[2,] 115 126 141 135 125 149 170 170 158 133 114 140
[3,] 145 150 178 163 172 178 199 199 184 162 146 166
[4,] 171 180 193 181 183 218 230 242 209 191 172 194
[5,] 196 196 236 235 229 243 264 272 237 211 180 201
[6,] 204 188 235 227 234 264 302 293 259 229 203 229
[7,] 242 233 267 269 270 315 364 347 312 274 237 278
[8,] 284 277 317 313 318 374 413 405 355 306 271 306
[9,] 315 301 356 348 355 422 465 467 404 347 305 336
[10,] 340 318 362 348 363 435 491 505 404 359 310 337
[11,] 360 342 406 396 420 472 548 559 463 407 362 405
[12,] 417 391 419 461 472 535 622 606 508 461 390 432
## R
> months <- c("Jan", "Feb", "Mar", "Apr", "May", "Jun",
> "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
## R
> monthplot(AirPassengers)
Figura 3-22. Ao usar a função monthplot(), podemos ver como o desempenho mensal muda
com o passar dos anos.
## R
> h = hist2d(t(matrix(AirPassengers, nrow = 12, ncol = 12)), 5, months)
> image(1:ncol(h), 1:nrow(h), t(h), col = heat.colors(5),
> axes = FALSE, xlab = "Time", ylab = "Passenger Count")
Figura 3-23. Mapa de calor construído a partir de nosso histograma 2D manual dos dados
do AirPassenger.
Figura 3-24. Histograma bidimensional de uma métrica de áudio para uma única palavra.
O gráfico à esquerda tem uma escala de contagem linear, enquanto o gráfico à direita tem
uma escala de contagem de transformação log.
## R
> w1 <- words[V1 == 1]
> plot(hexbin(w1))
## R
> require(plotly)
> require(data.table)
Figura 3-27. Outra perspectiva sobre os mesmos dados ilustra com mais clareza a tendência
crescente de um ano para o outro. Eu recomendo muito que execute o código em seu próprio
computador, no qual poderá rotacioná-lo você mesmo4.
Figura 3-29. Essa perspectiva do mesmo passeio aleatório é mais reveladora. Novamente, o
incentivo a testar esse código!
## python
>>> ## membership status
>>> years = ['2014', '2015', '2016', '2017', '2018']
>>> memberStatus = ['bronze', 'silver', 'gold', 'inactive']
## python
>>> def random_weekly_time_delta():
>>> days_of_week = [d for d in range(7)]
>>> hours_of_day = [h for h in range(11, 23)]
>>> minute_of_hour = [m for m in range(60)]
>>> second_of_minute = [s for s in range(60)]
>>> return pd.Timedelta(str(np.random.choice(days_of_week))
>>> + " days" ) +
>>> pd.Timedelta(str(np.random.choice(hours_of_day))
>>> + " hours" ) +
>>> pd.Timedelta(str(np.random.choice(minute_of_hour))
>>> + " minutes") +
>>> pd.Timedelta(str(np.random.choice(second_of_minute))
>>> + " seconds")
## python
>>> df.set_index(pd.to_datetime(df.timestamp), inplace = True)
>>> df.sort_index(inplace = True)
>>> df.groupby(pd.Grouper(freq='M')).amount.sum().plot()
## python
>>> import numpy as np
## python
>>> ids = taxi_id_number(10)
>>> print(next(ids))
>>> print(next(ids))
>>> print(next(ids))
7
2
5
## python
>>> def shift_info():
>>> start_times_and_freqs = [(0, 8), (8, 30), (16, 15)]
>>> indices = np.arange(len(start_times_and_freqs))
>>> while True:
>>> idx = np.random.choice(indices, p = [0.25, 0.5, 0.25])
>>> start = start_times_and_freqs[idx]
>>> yield (start[0], start[0] + 7.5, start[1])
## python
>>> from dataclasses import dataclass
>>> @dataclass
>>> class TimePoint:
>>> taxi_id: int
>>> name: str
>>> time: float
## python
>>> import queue
## python
>>> sim = Simulator(1000)
>>> sim.run()
## python
>>> ### CONFIGURAÇÃO
>>> ## Layout de física.
>>> N = 5 # width of lattice
>>> M = 5 # height of lattice
>>> ## Definições de temperatura.
>>> temperature = 0.5
>>> BETA = 1 / temperature
## python
>>> def magnetizationForState(state):
>>> return np.sum(state)
## python
>>> def mcmcAdjust(state):
>>> n = state.shape[0]
>>> m = state.shape[1]
>>> x, y = np.random.randint(0, n), np.random.randint(0, m)
>>> centerS = state[x, y]
>>> cost = costForCenterState(state, x, y, n, m)
>>> if cost < 0:
>>> centerS *= -1
>>> elif np.random.random() < np.exp(-cost * BETA):
>>> centerS *= -1
>>> state[x, y] = centerS
>>> return state
## python
>>> def runState(state, n_steps, snapsteps = None):
>>> if snapsteps is None:
>>> snapsteps = np.linspace(0, n_steps, num = round(n_steps / (M * N * 100)),
>>> dtype = np.int32)
>>> saved_states = []
>>> sp = 0
>>> magnet_hist = []
>>> for i in range(n_steps):
>>> state = mcmcAdjust(state)
>>> magnet_hist.append(magnetizationForState(state))
>>> if sp < len(snapsteps) and i == snapsteps[sp]:
>>> saved_states.append(np.copy(state))
>>> sp += 1
>>> return state, saved_states, magnet_hist
## python
>>> ### EXECUTA A SIMULAÇÃO
>>> init_state = initRandState(N, M)
>>> print(init_state)
>>> final_state = runState(np.copy(init_state), 1000)
Figura 4-3. Estado final de baixa temperatura em nossa simulação executada, conforme
visto em 1.000 intervalos de tempo.
Figura 4-4. Cem simulações independentes de possíveis formas pelas quais o sistema poderia
entrar em um estado magnetizado a temperatura baixa, mesmo quando cada site de lattice
original foi inicializado aleatoriamente.
Figura 5-1. Um recurso comum de um banco de dados desenvolvido para uso de séries
temporais é que a taxa de inserção de dados é constante em relação ao tamanho do banco
de dados. Uma vantagem surpreendente em comparação a uma solução de armazenamento
SQL tradicional.
CAPÍTULO 6
Modelos Estatísticos para Séries Temporais
53
Figura 6-2. PACF das séries temporais das ordens não transformadas exemplificadas
na Figura 6-1.
## R
> fit <- ar(demand[["Banking orders (2)"]], method = "mle")
> fit
Call:
ar(x = demand[["Banking orders (2)"]], method = "mle")
Coefficients:
1 2 3
-0.1360 -0.2014 -0.3175
## R
> est <- arima(x = demand[["Banking orders (2)"]],
> order = c(3, 0, 0))
> est
Call:
arima(x = demand[["Banking orders (2)"]], order = c(3, 0, 0))
Coefficients:
ar1 ar2 ar3 intercept
-0.1358 -0.2013 -0.3176 79075.350
s.e. 0.1299 0.1289 0.1296 2981.125
Call:
arima(x = demand[["Banking orders (2)"]],
order = c(3, 0, 0),
fixed = c(0, NA, NA, NA))
Coefficients:
ar1 ar2 ar3 intercept
0 -0.1831 -0.3031 79190.705
s.e. 0 0.1289 0.1298 3345.253
## R
> fixed <- c(0, NA, NA, NA)
## R
> acf(est.1$residuals)
Figura 6-3. ACF dos resíduos do modelo AR (3) que ajustamos ao forçar o parâmetro
lag – 1 em 0.
Box-Ljung test
data: est.1$residuals
X-squared = 9.3261, df = 7, p-value = 0.2301
## R
> require(forecast)
> plot(demand[["Banking orders (2)"]], type = 'l')
> lines(fitted(est.1), col = 3, lwd = 2) ## use o pacote forecast
Figura 6-4. Aqui vemos a série temporal original na linha sólida e, na linha tracejada, a série
temporal ajustada.
## R
> fitted(est.1, h = 3)
## R
> var(fitted(est.1, h = 3), na.rm = TRUE)
[1] 174,870,141
> var(fitted(est.1, h = 5), na.rm = TRUE)
[1] 32,323,722
> var(fitted(est.1, h = 10), na.rm = TRUE)
[1] 1,013,396
> var(fitted(est.1, h = 20), na.rm = TRUE)
[1] 1,176
> var(fitted(est.1, h = 30), na.rm = TRUE)
[1] 3.5
## R
> acf(demand[["Banking orders (2)"]])
Figura 6-7. Usamos a ACF da série temporal de demanda para determinar a ordem do
modelo MA.
Coefficients:
ma1 ma2 ma3 ma4 ma5 ma6 ma7 ma8 ma9 intercept
0 0 -0.4725 0 0 0 0 0 -0.0120 79689.81
s.e. 0 0 0.1459 0 0 0 0 0 0.1444 2674.60
## R
> Box.test(ma.est$residuals, lag = 10, type = "Ljung", fitdf = 3)
Box-Ljung test
data: ma.est$residuals
X-squared = 7.6516, df = 7, p-value = 0.3643
## R
> fitted(ma.est, h=1)
Time Series:
Start = 1
End = 60
Frequency = 1
[1] 90116.64 80626.91 74090.45 38321.61 74734.77 101153.20 65930.90
[8] 106351.80 104138.05 86938.99 102868.16 80502.02 81466.01 77619.15
[15] 100984.93 81463.10 61622.54 79660.81 88563.91 65370.99 104679.89
[22] 48047.39 73070.29 115034.16 80034.03 70052.29 70728.85 90437.86
[29] 80684.44 91533.59 101668.18 42273.27 93055.40 68187.65 75863.50
[36] 40195.15 82368.91 90605.60 69924.83 54032.55 90866.20 85839.41
[43] 64932.70 43030.64 85575.32 76561.14 82047.95 95683.35 66553.13
[50] 89532.20 85102.64 80937.97 93926.74 47468.84 75223.67 100887.60
[57] 92059.32 84459.85 67112.16 80917.23
## R
> fitted(ma.est, h=10)
Time Series:
Start = 1
End = 60
Frequency = 1
[1] NA NA NA NA NA NA NA NA
[9] NA NA 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81
[17] 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81
[25] 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81
[33] 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81
[41] 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81 79689.81
## R
> require(forecast)
> set.seed(1017)
> ## ordem do modelo arima escondida de propósito
> y = arima.sim(n = 1000, list(ar = ###, ma = ###))
Figura 6-10. ACF e PACF dos resíduos de um modelo ARIMA (1, 0, 1).
## R
> ar2.ma1.model = Arima(y, order = c(2, 0, 1))
> plot(y, type = 'l')
> lines(ar2.ma1.model$fitted, col = 2)
> plot(y, ar2.ma1.model$fitted)
> par(mfrow = c(2,1))
> acf(ar2.ma1.model$residuals)
> pacf(ar2.ma1.model$residuals)
## R
> ar2.ma2.model = Arima(y, order = c(2, 0, 2))
> plot(y, type = 'l')
> lines(ar2.ma2.model$fitted, col = 2)
> plot(y, ar2.ma2.model$fitted)
> par(mfrow = c(2,1))
> acf(ar2.ma2.model$residuals)
> pacf(ar2.ma2.model$residuals)
>
> ar2.d1.ma2.model = Arima(y, order = c(2, 1, 2))
> plot(y, type = 'l')
> lines(ar2.d1.ma2.model$fitted, col = 2)
> plot(y, ar2.d1.ma2.model$fitted)
> par(mfrow = c(2,1))
> acf(ar2.d1.ma2.model$residuals)
> pacf(ar2.d1.ma2.model$residuals)
## R
> cor(y, ar1.ma1.model$fitted)
[1] 0.3018926
> cor(y, ar2.ma1.model$fitted)
[1] 0.4683598
> cor(y, ar2.ma2.model$fitted)
[1] 0.4684905
> cor(y, ar2.d1.ma2.model$fitted)
[1] 0.4688166
## R
## original coefficients
> y = arima.sim(n = 1000, list(ar = c(0.8, -0.4), ma = c(-0.7)))
> ar2.ma1.model$coef
ar1 ar2 ma1 intercept
0.785028320 -0.462287054 -0.612708282 -0.005227573
Coefficients:
ma1 ma2 ma3 mean
-0.0645 -0.1144 -0.4796 79914.783
s.e. 0.1327 0.1150 0.1915 1897.407
## R
> auto.model = auto.arima(y)
> auto.model
Series: y
ARIMA(2,0,1) with zero mean
Coefficients:
ar1 ar2 ma1
0.7847 -0.4622 -0.6123
## R
> VARselect(demand[, 11:12, with = FALSE], lag.max=4,
+ type="const")
$selection
AIC(n) HQ(n) SC(n) FPE(n)
3 3 1 3
$criteria
1 2 3 4
AIC(n) 3.975854e+01 3.967373e+01 3.957496e+01 3.968281e+01
HQ(n) 3.984267e+01 3.981395e+01 3.977126e+01 3.993521e+01
SC(n) 3.997554e+01 4.003540e+01 4.008130e+01 4.033382e+01
FPE(n) 1.849280e+17 1.700189e+17 1.542863e+17 1.723729e+17
## R
> est.var <- VAR(demand[, 11:12, with = FALSE], p=3, type="const")
> est.var
Figura 6-12. Na parte superior, vemos os valores reais (linha contínua) versus os valores
preditos (linha tracejada) para as ordens bancárias (2) e, na parte inferior, vemos o mesmo
para as ordens bancárias (3). Curiosamente, o gráfico superior se parece mais com uma
previsão comum, em que a previsão é um pouco “lenta” na mudança em relação aos
dados reais, ao passo que no gráfico inferior vemos que a previsão predisse mudanças um
pouco antes de acontecerem. Isso sugere que as ordens bancárias (2) “lideram” as ordens
bancárias (3), o que significa que as ordens bancárias (2) são úteis na predição de ordens do
controlador de tráfego, mas não o contrário, ou pelo menos não em tal grau.
## R
> serial.test(est.var, lags.pt = 8, type="PT.asymptotic")
## R
## Foguete levará 100 intervalos de tempo.
ts.length <- 100
## R
par(mfrow = c(3, 1))
plot(x, main = "Position", type = 'l')
plot(v, main = "Velocity", type = 'l')
plot(acceleration, main = "Acceleration", type = 'l')
## R
z <- x + rnorm(ts.length, sd = 300)
plot (x, ylim = range(c(x, z)))
lines(z)
Figura 7-1. A posição, a velocidade e a aceleração do nosso foguete.
Figura 7-2. A verdadeira posição (pontos) versus nossa medição ruidosa (linha). Repare
que a posição x não retrata uma parábola perfeita devido ao ruído que inserimos na
equação de transição de estado.
for (k in 2:ts.length) {
# Atualização do tempo.
xhatminus[k, ] <- A %*% matrix(xhat[k-1, ])
Pminus[k, , ] <- A %*% P[k-1, , ] %*% t(A) + Q
## R
## Parâmetros de ruído:
R <- 10^2 ## Variância de medição — este valor deve ser definido de acordo com
## os limites físicos conhecidos da ferramenta de medição.
## Nós o definimos de acordo com o ruído que adicionamos a x
## para produzir x na geração de dados acima da variância do processo,
Q <- 10 ## geralmente considerado um hiperparâmetro
## a ser ajustado a fim de maximizar o desempenho.
## Parâmetros dinâmicos:
Figura 7-3. Muitas grandezas relacionadas: a posição medida (linha sólida); a posição
real/verdadeira (linha tracejada); a estimativa de filtragem da posição (ou seja, a
melhor estimativa para a posição no tempo t com a medição no tempo t incorporada,
representada pela linha pontilhada); e a previsão da posição (ou seja, a melhor
estimativa para a posição no tempo t incorporada somente à dinâmica conhecida
somada às medições até o tempo t – 1, não incluindo o tempo t. Grandeza representada
pela linha mista).
Figura 7-6. O algoritmo de Viterbi busca todos os caminhos possíveis para explicar uma
dada série de tempo observada, em que um caminho indica qual estado foi ocupado em
cada intervalo de tempo.
## R
> table(true.mean)
true.mean
-0.03 0.02 0.1
155 103 58
## R
require(depmixS4)
hmm.model <- depmix(returns ~ 1, family = gaussian(),
nstates = 4, data=data.frame(returns=returns))
model.fit <- fit(hmm.model)
post_probs <- posterior(model.fit)
Figura 7-7. Nesse gráfico, o segundo plano indica um estado distinto e a linha preta
sólida representa os valores reais. Os tipos de objetos de linha branca vertical são, na
verdade, porções muito estreitas que representam os tempos, nos quais se estima que o
processo esteja no que acaba sendo o mais raro dos quatro estados.
-0.09190191
sd 0.03165587
[[2]]
[[2]][[1]] <- Poderia ser consistente com o modelo bear market.
Model of type gaussian (identity), formula: returns ~ 1
Coefficients:
(Intercept)
-0.05140387
sd 0.2002024
[[3]]
[[3]][[1]] <- Poderia ser consistente com o modelo bull market.
Model of type gaussian (identity), formula: returns ~ 1
Coefficients:
(Intercept)
0.0853683
sd 0.07115133
[[4]]
[[4]][[1]] <- Poderia ser um pouco consistente com o modelo neutro.
Model of type gaussian (identity), formula: returns ~ 1
Coefficients:
(Intercept)
-0.0006163519
sd 0.0496334
## R
elec = fread("electric.csv")
require(bsts)
n = colnames(elec)[9]
par(mfrow = c(2, 1))
plot(elec[[n]][1:4000])
plot(elec[[n]][1:96)
## Conforme analisado anteriormente no livro,
## a escala temporal apropriada é a chave para a compreensão dos dados ts.
## R
ss <- AddLocalLinearTrend(list(), elec[[n]])
ss <- AddSeasonal(ss, elec[[n]], nseasons = 24, season.duration = 1 )
ss <- AddSeasonal(ss, elec[[n]], nseasons = 7, season.duration = 24)
## R
model1 <- bsts(elec[[n]],
state.specification = ss,
niter = 100)
plot(model1, xlim = c(1800, 1900)
## R
plot(model1, "seasonal", nseasons = 7, season.duration = 24)
Figura 7-10. Distribuições de contribuições da tendência dos dados, bem como dos
componentes sazonais diários e do dia da semana. Ao somar esses três componentes,
você obterá o valor da predição.
••
pred <- predict(model1, horizon = 24, quantiles = c(0.05, 0.95))
plot(pred, plot.original = 72)
Periodogramas
77
Figura 8-2. Exemplos de características de séries temporais que os profissionais da saúde
usam para ler dados de séries temporais de um EKG.
Figura 8-3. Na série temporal de índice glicêmico de um dia, podemos ver que existem
quatro características que a maioria dos profissionais de saúde identificaria em um
dia típico, e uma delas não está relacionada à alimentação, mas é conhecida como o
“ fenômeno do amanhecer” [Dawn Phenomenon] ou “ fenômeno do alvorecer”. É o tipo
de dado que desejaríamos identificar em uma ou várias características.
## python
>> from tsfresh.examples.robot_execution_failures import
download_robot_execution_failures,
load_robot_execution_failures
>> download_robot_execution_failures()
>> timeseries, y = load_robot_execution_failures()
## python
>> extracted_features.columns
Index(['F_x__abs_energy', 'F_x__absolute_sum_of_changes',
'F_x__agg_autocorrelation__f_agg_"mean"',
'F_x__agg_autocorrelation__f_agg_"median"',
'F_x__agg_autocorrelation__f_agg_"var"',
'F_x__agg_linear_trend__f_agg_"max"__chunk_len_10__attr_
"intercept"',
'F_x__agg_linear_trend__f_agg_"max"__chunk_len_10__attr_
"rvalue"',
'F_x__agg_linear_trend__f_agg_"max"__chunk_len_10__attr_
"slope"',
'F_x__agg_linear_trend__f_agg_"max"__chunk_len_10__attr_
"stderr"',
'F_x__agg_linear_trend__f_agg_"max"__chunk_len_50__attr_
"intercept"',
...
'T_z__time_reversal_asymmetry_statistic__lag_1',
'T_z__time_reversal_asymmetry_statistic__lag_2',
'T_z__time_reversal_asymmetry_statistic__lag_3',
'T_z__value_count__value_-inf', 'T_z__value_count__value_0',
'T_z__value_count__value_1', 'T_z__value_count__value_inf',
'T_z__value_count__value_nan', 'T_z__variance',
'T_z__variance_larger_than_standard_deviation'],
dtype='object', name='variable', length=4764)
## R
>> x_idx = random.sample(range(len(features_filtered.columns)), 10)
>> selX = features_filtered.iloc[:, x_idx].values
>> unselected_features = list(set(extracted_features.columns)
.difference(set(features_filtered.columns)))
>> unselected_features = random.sample(unselected_features, 10)
>> unsel_x_idx = [idx for (idx, val) in enumerate(
extracted_features.columns) if val in unselected_features]
>> unselX = extracted_features.iloc[:, unsel_x_idx].values
>> mixed_X = np.hstack([selX, unselX])
## python
>>> from cesium import datasets
>>> eeg = datasets.fetch_andrzejak()
## python
>>> plt.subplot(3, 1, 1)
>>> plt.plot(eeg["measurements"][0])
>>> plt.legend(eeg['classes'][0])
>>> plt.subplot(3, 1, 2)
>>> plt.plot(eeg["measurements"][300])
>>> plt.legend(eeg['classes'][300])
>>> plt.subplot(3, 1, 3)
>>> plt.plot(eeg["measurements"][450])
>>> plt.legend(eeg['classes'][450])
Figura 9-1. Plotamos três amostras selecionadas aleatoriamente do conjunto de dados EEG.
As amostras são independentes e não medições contemporâneas de diferentes partes do
mesmo cérebro. Cada uma é uma medição de série temporal independente feita com um
paciente diferente em um momento distinto.
## python
>>> from cesium import featurize.featurize_time_series as ft
>>> features_to_use = ["amplitude",
>>> "percent_beyond_1_std",
>>> "percent_close_to_median",
>>> "skew",
>>> "max_slope"]
>>> fset_cesium = ft(times = eeg["times"],
>>> values = eeg["measurements"],
>>> errors = None,
>>> features_to_use = features_to_use,
>>> scheduler = None)
Figura 9-2. Valores numéricos das características que geramos para as primeiras amostras
em nosso conjunto de dados.
Análise Prática de Séries Temporais | 81
## python
>>> np.std(eeg_small["measurements"][0])
40.411
>>> np.mean(eeg_small["measurements"][0])
-4.132
>>> sample_ts = eeg_small["measurements"][0]
>>> sz = len(sample_ts)
>>> ll = -4.13 - 40.4
>>> ul = -4.13 + 40.4
>>> quals = [i for i in range(sz) if sample_ts[i] < ll or
sample_ts[i] > ul ]
>>> len(quals)/len(ser)
0.327 ## Compatível com a característica gerada na Figura 9-2
## python
>>> from sklearn.ensemble import RandomForestClassifier
>>> rf_clf = RandomForestClassifier(n_estimators = 10,
>>> max_depth = 3,
>>> random_state = 21)
>>> rf_clf.fit(X_train, y_train)
## python
>>> rf_clf.score(X_test, y_test)
0.616
## python
>>> import xgboost as xgb
>>> xgb_clf = xgb.XGBClassifier(n_estimators = 10,
>>> max_depth = 3,
>>> random_state = 21)
>>> xgb_clf.fit(X_train, y_train)
>>> xgb_clf.score(X_test, y_test)
0.648
## Random Forest
>>> start = time.time()
>>> rf_clf.fit(X_train, y_train)
>>> end = time.time()
>>> end - start
0.027
## python
## python
## Testa
>>> ##
>>> Testa oo mesmo
mesmo número
número de
de árvores
árvores (10),
(10), porém
porém com
com menos
menos complexidade
complexidade
## (max_depth
>>> ##
>>> (max_depth == 2)
2)
>>>
>>>
>>> ##
>>> ## XGBoost
XGBoost
>>>
>>> xgb_clf
xgb_clf == xgb.XGBClassifier(n_estimators
xgb.XGBClassifier(n_estimators == 10,
10,
max_depth
max_depth == 2,
2,
random_state = 21)
random_state = 21)
>>>
>>> xgb_clf.fit(X_train,
xgb_clf.fit(X_train, y_train)
y_train)
>>> xgb_clf.score(X_test, y_test)
>>> xgb_clf.score(X_test, y_test)
0.616
0.616
>>> ##
>>> Random Forest
## Random Forest
>>> rf_clf
>>> rf_clf == RandomForestClassifier(n_estimators
RandomForestClassifier(n_estimators == 10,
10,
max_depth
max_depth == 2,
2,
random_state
random_state == 21)
21)
>>>
>>> rf_clf.fit(X_train,
rf_clf.fit(X_train, y_train)
y_train)
>>> rf_clf.score(X_test, y_test)
>>> rf_clf.score(X_test, y_test)
0.544
0.544
Isso vale até mesmo quando reduzimos ainda mais a complexidade da árvore:
>>> ## Testa o mesmo número de árvores (10), porém com menos complexidade
>>> ## (max_depth = 1)
>>> ## XGBoost
>>> xgb_clf = xgb.XGBClassifier(n_estimators = 10,
max_depth = 1,
random_state = 21)
>>> xgb_clf.fit(X_train, y_train)
>>> xgb_clf.score(X_test, y_test)
0.632
## python
>>> plt.subplot(3, 2, 1)
>>> plt.plot(words.iloc[1, 1:-1])
>>> plt.title("Word = " + str(words.word[1]), fontweight = 'bold')
>>> plt.subplot(3, 2, 2)
>>> plt.hist(words.iloc[1, 1:-1], 10)
>>> plt.subplot(3, 2, 3)
>>> plt.plot(words.iloc[3, 1:-1])
>>> plt.title("Word = " + str(words.word[3]), fontweight = 'bold')
>>> plt.subplot(3, 2, 4)
>>> plt.hist(words.iloc[3, 1:-1], 10)
>>> plt.subplot(3, 2, 5)
>>> plt.plot(words.iloc[5, 1:-1])
>>> plt.title("Word = " + str(words.word[11]), fontweight = 'bold')
>>> plt.subplot(3, 2, 6)
>>> plt.hist(words.iloc[5, 1:-1], 10)
## python
>>> x = np.array([])
>>> y = np.array([])
>>>
>>> w = 12
>>> selected_words = words[words.word == w]
>>> selected_words.shape
>>>
>>> for idx, row in selected_words.iterrows():
>>> y = np.hstack([y, row[1:271]])
>>> x = np.hstack([x, np.array(range(270))])
>>>
>>> fig, ax = plt.subplots()
Figura 9-7. Um histograma 2D das projeções de palavra 1D para palavra = 23. O eixo y é
o valor em um determinado intervalo de tempo e o eixo x representa os 270 intervalos de
tempo para cada amostra de série temporal/projeção de palavra.
## python
>>> word_vals = words.iloc[:, 1:271]
>>> times = []
>>> word_values = []
>>> for idx, row in word_vals.iterrows():
>>> word_values.append(row.values)
>>> times.append(np.array([i for i in range(row.values.shape[0])]))
>>>
>>> features_to_use = ['amplitude',
>>> 'percent_beyond_1_std',
>>> 'percent_close_to_median']
>>> featurized_words = ft(times = times,
>>> values = word_values,
>>> errors = None,
>>> features_to_use = features_to_use,
>>> scheduler = None)
## python
>>> ## Cria algumas características derivadas do histograma
>>> times = []
>>> hist_values = []
>>> for idx, row in words_features.iterrows():
>>> hist_values.append(np.histogram(row.values,
>>> bins=10,
>>> range=(-2.5, 5.0))[0] + .0001)
>>> ## 0s cause downstream problems
>>> times.append(np.array([i for i in range(9)]))
>>>
>>> features_to_use = ["amplitude",
>>> "percent_close_to_median",
>>> "skew"
>>> ]
>>>
>>> featurized_hists = ft(times = times,
>>> values = hist_values,
>>> errors = None,
>>> features_to_use = features_to_use,
>>> scheduler = None)
## python
>>> features = pd.concat([featurized_words.reset_index(drop=True),
>>> featurized_hists],
>>> axis=1)
## python
>>> def distDTW(ts1, ts2):
>>> ## Aqui é a configuração
>>> DTW={}
>>> for i in range(len(ts1)):
>>> DTW[(i, -1)] = np.inf
>>> for i in range(len(ts2)):
>>> DTW[(-1, i)] = np.inf
>>> DTW[(-1, -1)] = 0
>>>
>>> ## Aqui, é onde realmente calculamos o ideal,
>>> ## um passo de cada vez.
>>> for i in range(len(ts1)):
>>> for j in range(len(ts2)):
>>> dist = (ts1[i] - ts2[j])**2
>>> DTW[(i, j)] = dist + min(DTW[(i-1, j)],
>>> DTW[(i, j-1)],
>>> DTW[(i-1, j-1)])
>>> ## Este é um exemplo de programação dinâmica
>>>
>>> ## Quando encontramos o caminho completo,
>>> ## retornamos a distância associada.
>>> return sqrt(DTW[len(ts1)-1, len(ts2)-1])
## python
>>> from sklearn import preprocessing
>>> feature_values = preprocessing.scale(features.values)
## python
>>> from sklearn.metrics.cluster import homogeneity_score
>>> homogeneity_score(words.word, words.feature_labels)
0.508
## python
>>> p = pairwise_distances(X, metric = distDTW)
>>> ## Isso leva tempo para ser calculado então vale a pena salvar
>>> with open("pairwise_word_distances.npy", "wb") as f:
np.save(f, p)
## python
>>> from sklearn.cluster import AgglomerativeClustering
>>> dtw_clustering = AgglomerativeClustering(linkage = 'average',
>>> n_clusters = 50,
>>> affinity = 'precomputed')
>>> words['dtw_labels'] = dtw_clustering.fit_predict(p)
## python
>>> from sklearn.metrics.cluster import homogeneity_score,
>>> completeness_score
>>> homogeneity_score(words.word, words.dtw_labels)
0.828
>>> completeness_score(words.word, words.dtw_labels)
0.923
## python
>>> import mxnet as mx
>>> fc1 = mx.gluon.nn.Dense(120, activation='relu')
## python
>>> ## Cria uma rede em vez de uma camada independente.
>>> from mx.gluon import nn
>>> net = nn.Sequential()
>>> net.add(nn.Dense(120, activation='relu'),
>>> nn.Dense(1))
>>> net.initialize(init=init.Xavier())
>>>
>>> ## Define a perda que queremos.
>>> L2Loss = gluon.loss.L2Loss()
>>>
>>> trainer = gluon.Train(net.collect_params(), 'sgd',
>>> {'learning_rate': 0.01})
## python
>>> for data,target in train_data:
>>> ## Calcula o gradiente.
>>> with autograd.record():
>>> out = net(data)
>>> loss = L2Loss(output, data)
>>> loss.backward()
>>> ## Aplica o gradiente para atualizar os parâmetros.
>>> trainer.step(batch_size)
## python
>>> net.save_parameters('model.params')
## R
> elec = fread("electricity.txt")
> elec
V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17 V18
1: 14 69 234 415 215 1056 29 840 226 265 179 148 112 171 229 1001 49 162
2: 18 92 312 556 292 1363 29 1102 271 340 235 192 143 213 301 1223 64 216
3: 21 96 312 560 272 1240 29 1025 270 300 221 171 132 185 261 1172 61 197
4: 20 92 312 443 213 845 24 833 179 211 170 149 116 151 209 813 40 173
5: 22 91 312 346 190 647 16 733 186 179 142 170 99 136 148 688 29 144
## R
> ncol(elec)
[1] 321
> nrow(elec)
[1] 26304
## R
> elec[125:148, plot(V4, type = 'l', col = 1, ylim = c(0, 1000))]
> elec[125:148, lines(V14, type = 'l', col = 2)]
> elec[125:148, lines(V114, type = 'l', col = 3)]
## R
> elec[1:168, plot(V4, type = 'l', col = 1, ylim = c(0, 1000))]
> elec[1:168, lines(V14, type = 'l', col = 2)]
> elec[1:168, lines(V114, type = 'l', col = 3)]
Figura 10-3. Um ciclo completo de sete dias dos dados amostrados para os mesmos três
locais, conforme ilustrado no gráfico diário. Nossa sensação de um padrão diário é
corroborada com essa visão mais ampla dos dados, indicando que um grande pico por
dia junto com algumas características menores parece consistente no comportamento.
## R
> elec.diff[1:168, plot( V4, type = 'l', col = 1, ylim = c(-350, 350))]
> elec.diff[1:168, lines(V14, type = 'l', col = 2)]
> elec.diff[1:168, lines(V114, type = 'l', col = 3)]
## python
>>> from math import floor
>>>
>>> ## Para arquivamento.
>>> import os
>>> import argparse
>>>
>>> ## Módulo de aprendizado profundo.
>>> import mxnet as mx
>>>
>>> ## Processamento de dados.
>>> import numpy as np
>>> import pandas as pd
>>>
>>> ## Relatórios customizados.
>>> import perf
## python
>>> ## Alguns hiperparâmetros que não ajustaremos por meio de entradas de linha de comando
>>> DATA_SEGMENTS = { 'tr': 0.6, 'va': 0.2, 'tst': 0.2}
## python
>>> ################################
>>> ## PREPARAÇÃO DOS DADOS ##
>>> ################################
>>>
>>> def prepare_iters(data_dir, win, h, model, batch_n):
>>> X, Y = prepared_data(data_dir, win, h, model)
>>>
>>> n_tr = int(Y.shape[0] * DATA_SEGMENTS['tr'])
>>> n_va = int(Y.shape[0] * DATA_SEGMENTS['va'])
>>>
>>> X_tr, X_valid, X_test = X[ : n_tr],
>>> X[n_tr : n_tr + n_va],
>>> X[n_tr + n_va : ]
>>> Y_tr, Y_valid, Y_test = Y[ : n_tr],
## python
>>> def prepared_data(data_dir, win, h, model_name):
>>> df = pd.read_csv(os.path.join(data_dir, 'electricity.diff.txt'),
>>> sep=',', header=0)
>>> x = df.as_matrix()
>>> ## Normaliza os dados. Isso cria um lookahead, já que
>>> ## normalizamos baseados nos valores medidos no conjunto
>>> ## de dados. Em um pipeline menos básico, calcularíamos
>>> ## isso como uma média móvel para evitar o lookahead.
>>> x = (x - np.mean(x, axis = 0)) / (np.std(x, axis = 0))
>>>
>>> if model_name == 'fc_model': ## NC data format
## python
>>> x = (x - np.mean(x, axis = 0)) / (np.std(x, axis = 0))
## python
>>> if model_name == 'fc_model': ## NC data format
>>> ## Fornece o primeiro e o segundo lookbacks em uma entrada achatada.
>>> X = np.hstack([x[1:-h], x[0:-(h+1)]])
>>> Y = x[(h+1):]
## python
>>> # Pré-aloca X e Y.
>>> # X shape = num examples * time win * num channels (NTC)
>>> X = np.zeros((x.shape[0] - win - h, win, x.shape[1]))
>>> Y = np.zeros((x.shape[0] - win - h, x.shape[1]))
>>>
>>> for i in range(win, x.shape[0] - h):
>>> ## O valor alvo/rótulo está h passos à frente.
>>> Y[i-win] = x[i + h - 1 , :]
>>> ## Os dados de entrada são os passos anteriores do win.
>>> X[i-win] = x[(i - win) : i , :]
## python
(Pdb) Y[0, 1:10] == x[98, 1:10]
array([ True, True, True, True, True, True, True, True, True])
(Pdb) X.shape
(26204, 96, 321)
(Pdb) X[0, :, 1] == x[0:96, 1]
array([ True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
## python
>>> ## Boilerplate do mxnet.
>>> ## O padrão é 1 gpu cujo índice é 0
>>> devs = [mx.gpu(0)]
>>> module = mx.mod.Module(symbol,
>>> data_names=data_names,
>>> label_names=label_names,
>>> context=devs)
>>> module.bind(data_shapes=iter_train.provide_data,
>>> label_shapes=iter_train.provide_label)
>>> module.init_params(mx.initializer.Uniform(0.1))
>>> module.init_optimizer(optimizer='adam',
>>> optimizer_params={'learning_rate':
>>> args.lr})
## python
>>> ## Resultados do treinamento.
>>> train_pred = module.predict(iter_train).asnumpy()
>>> train_label = iter_train.label[0][1].asnumpy()
>>> train_perf = evaluate_and_write(train_pred, train_label,
>>> save_dir, 'train', epoch)
>>>
>>> ## Resultados da validação.
>>> val_pred = module.predict(iter_val).asnumpy()
>>> val_label = iter_val.label[0][1].asnumpy()
>>> val_perf = evaluate_and_write(val_pred, val_label,
>>> save_dir, 'valid', epoch)
##
## python
python
>>>
>>> ##
if Resultados
epoch > 0: do treinamento.
>>>
>>> train_pred = module.predict(iter_train).asnumpy()
buf.append(val_perf['COR'] - old_val)
>>>
>>> train_label
if epoch > 2:= iter_train.label[0][1].asnumpy()
>>>
>>> train_perf = evaluate_and_write(train_pred, train_label,
vals = buf.get()
>>>
>>> vals = [v for v in vals if v save_dir,
!= 0] 'train', epoch)
>>>
>>> if sum([v < COR_THRESHOLD for v in vals]) == len(vals):
>>>
>>> ## Resultados da validação. EXIT')
print_to_file('EARLY
>>>
>>> val_predbreak
= module.predict(iter_val).asnumpy()
>>>
>>> val_label
old_val = =val_perf['COR']
iter_val.label[0][1].asnumpy()
>>> val_perf = evaluate_and_write(val_pred, val_label,
>>>
## python save_dir, 'valid', epoch)
>>> def evaluate_and_write(pred, label, save_dir, mode, epoch):
>>> if not os.path.exists(save_dir):
>>> os.makedirs(save_dir)
>>>
>>> pred_df = pd.DataFrame(pred)
>>> label_df = pd.DataFrame(label)
>>> pred_df.to_csv( os.path.join(save_dir, '%s_pred%d.csv'
>>> % (mode, epoch)))
>>> label_df.to_csv(os.path.join(save_dir, '%s_label%d.csv'
>>> % (mode, epoch)))
>>>
>>> return { 'COR': COR(label,pred) }
## python
>>> if __name__ == '__main__':
## python
def print_to_file(msg):
## python
>>> def fc_model(iter_train, window, filter_size, num_filter, dropout):
>>> X = mx.sym.Variable(iter_train.provide_data[0].name)
>>> Y = mx.sym.Variable(iter_train.provide_label[0].name)
>>>
>>> output = mx.sym.FullyConnected(data=X, num_hidden = 20)
>>> output = mx.sym.Activation(output, act_type = 'relu')
>>> output = mx.sym.FullyConnected(data=output, num_hidden = 10)
>>> output = mx.sym.Activation(output, act_type = 'relu')
>>> output = mx.sym.FullyConnected(data = output, num_hidden = 321)
>>>
>>> loss_grad = mx.sym.LinearRegressionOutput(data = output,
>>> label = Y)
>>> return loss_grad, [v.name for v in iter_train.provide_data],
>>> [v.name for v in iter_train.provide_label]
Figura 10-5. A função tanh exibe comportamento não linear em valores pequenos antes
de se tornar funcionalmente constante, com uma derivada zero, em valores mais altos.
Figura 10-6. Apesar de a função ReLU ser fácil de ser calcular, ela introduz
uma não linearidade.
## python
>>> def cnn_model(iter_train, input_feature_shape, X, Y,
>>> win, sz_filt, n_filter, drop):
>>> conv_input = mx.sym.reshape(data=X, shape=(0, 1, win, -1))
>>> ## A convolução espera entrada 4d (N x channel x height x width)
>>> ## em nosso caso canal = 1 (semelhante a uma imagem em preto e branco.)
>>> ## height = time e width = canais recortam os locais de energia elétrica.
>>>
>>> cnn_output = mx.sym.Convolution(data=conv_input,
0 0.330701 0.292515
1 0.389125 0.349906
2 0.443271 0.388266
3 0.491140 0.442201
4 0.478684 0.410715
5 0.612608 0.564204
6 0.581578 0.543928
7 0.633367 0.596467
8 0.662014 0.586691
9 0.699139 0.600454
10 0.692562 0.623640
11 0.717497 0.650300
12 0.710350 0.644042
13 0.715771 0.651708
14 0.717952 0.651409
15 0.712251 0.655117
16 0.708909 0.645550
17 0.696493 0.650402
18 0.695321 0.634691
19 0.672669 0.620604
20 0.662301 0.597580
21 0.680593 0.631812
22 0.670143 0.623459
23 0.684297 0.633189
24 0.660073 0.604098
TESTING PERFORMANCE
{'COR': 0.5561901}
## python
>>> ## Esse código foi escrito para funcionar com pesos exportados
## python
>>> ## da
EsseTensorFlow:
código foi escrito para funcionar com pesos exportados
>>> ## https://www.tensorflow.org/api_docs/python/tf/contrib/cudnn_rnn/CudnnGRU
da TensorFlow:
>>> ## Mas pode ser facilmente reaproveitado para acomodar outros pesos:
https://www.tensorflow.org/api_docs/python/tf/contrib/cudnn_rnn/CudnnGRU
>>> def calc_gru(X,
## Mas pode ser weights, num_inputs,
facilmente num_features):
reaproveitado para acomodar outros pesos:
>>> def Us = weights[:(3*num_features*num_inputs)]
calc_gru(X, weights, num_inputs, num_features):
>>> Us = np.reshape(Us, [3, num_features, num_inputs])
weights[:(3*num_features*num_inputs)]
>>> Us = np.reshape(Us, [3, num_features, num_inputs])
>>> Ws = weights[(3*num_features*num_inputs):(3*num_features*num_features +
>>> 3*num_features*num_inputs)]
Ws = weights[(3*num_features*num_inputs):(3*num_features*num_features +
>>> Ws = np.reshape(Ws, [3, num_features, num_features])
3*num_features*num_inputs)]
>>> Ws = np.reshape(Ws, [3, num_features, num_features])
>>> Bs = weights[(-6 * num_features) :]
>>> Bs = weights[(-6
np.reshape(Bs, [6, num_features])
* num_features) :]
>>> s ==np.zeros([129,
Bs np.reshape(Bs,num_features])
[6, num_features])
>>> sh = np.zeros([129, num_features])
>>> h = np.zeros([129, num_features])
>>> for t in range(X.shape[0]):
>>> for tz in
= sigmoid(np.matmul(Us[0,
range(X.shape[0]): :, :], X[t, :]) +
>>> np.matmul(Ws[0, :, :], s[t, :]) + :,
z = sigmoid(np.matmul(Us[0, Bs[0,
:],:] + Bs[3,
X[t, :]) +:])
r = sigmoid(np.matmul(Us[1,
>>> np.matmul(Ws[0, :, :],:]X[t,
:, :], s[t, :]) + Bs[0, :]) +:])
+ Bs[3,
>>> np.matmul(Ws[1, :, :], s[t, :]) + :,
r = sigmoid(np.matmul(Us[1, Bs[1,
:],:] + Bs[4,
X[t, :]) +:])
h[t+1, :] :,
>>> np.matmul(Ws[1, = np.tanh(np.matmul(Us[2,
:], s[t, :]) + Bs[1, :]:, :], X[t,
+ Bs[4, :]):]) +
>>> Bs[2, h[t+1,
:] + :] = np.tanh(np.matmul(Us[2, :, :], X[t, :]) +
>>> Bs[2,
r*(np.matmul(Ws[2,
:] + :, :], s[t, :]) + Bs[5, :]))
>>> r*(np.matmul(Ws[2,
s[t+1, :] = (1
:,-:],
z)*h[t
s[t,+:])
1, +:]Bs[5,
+ z*s[t,
:])):]
>>> s[t+1, :] = (1 - z)*h[t + 1, :] + z*s[t, :]
>>> return h, s
>>> return h, s
TESTING PERFORMANCE
{'COR': 0.36212805}
## python
>>> ## Elemento recorrente
>>> stacked_rnn_cells = mx.rnn.SequentialRNNCell()
>>> stacked_rnn_cells.add(mx.rnn.GRUCell(num_hidden=args.rnn_units))
>>> outputs, _ = stacked_rnn_cells.unroll(length=win,
>>> inputs=cnn_output,
>>> merge_outputs=False)
>>>
>>> rnn_output = outputs[-1]
>>> n_outputs = input_feature_shape[2]
>>> cnn_rnn_model = mx.sym.FullyConnected(data=rnn_output,
>>> num_hidden=n_outputs)
## python
>>> ## Elemento ar
>>> ar_outputs = []
>>> for i in list(range(input_feature_shape[2])):
>>> ar_series = mx.sym.slice_axis(data=X,
>>> axis=2,
>>> begin=i,
>>> end=i+1)
>>> fc_ar = mx.sym.FullyConnected(data=ar_series, num_hidden=1)
>>> ar_outputs.append(fc_ar)
>>> ar_model = mx.sym.concat(*ar_outputs, dim=1)
TESTING PERFORMANCE
{'COR': 0.7622162}
111
## R
> require(forecast)
>
> phi <- 0.7
> time_steps <- 24
> N <- 1000
> sigma_error <- 1
>
> sd_series <- sigma_error^2 / (1 - phi^2)
> starts <- rnorm(N, sd = sqrt(sd_series))
> estimates <- numeric(N)
> res <- numeric(time_steps)
>
> for (i in 1:N) {
> errs = rnorm(time_steps, sd = sigma_error)
> res[1] <- starts[i] + errs[1]
>
> for (t in 2:time_steps) {
> res[t] <- phi * tail(res, 1) + errs[t]
> }
> estimates <- c(estimates, arima(res, c(1, 0, 0))$coef[1])
> }
>
> hist(estimates,
> main = "Estimated Phi for AR(1) when ts is AR(1)",
> breaks = 50)
## R
> ## Agora vamos assumir que temos um verdadeiro processo AR (2),
> ## e como isso é mais complicado, vamos mudar para arima.sim
> phi_1 <- 0.7
> phi_2 <- -0.2
>
> estimates <- numeric(N)
> for (i in 1:N) {
> res <- arima.sim(list(order = c(2,0,0),
> ar = c(phi_1, phi_2)),
> n = time_steps)
> estimates[i] <- arima(res, c(1, 0, 0))$coef[1]
> }
>
> hist(estimates,
> main = "Estimated Phi for AR(1) when ts is AR(2)",
> breaks = 50)
## R
> summary(estimates)
Min. 1st Qu. Median Mean 3rd Qu. Max.
-0.5252 0.4766 0.6143 0.5846 0.7215 0.9468
## python
>>> def array_to_ts(arr):
>>> idx = 0
>>> while idx + 6 <= arr.shape[0]:
>>> yield arr[idx:(idx+6)]
CAPÍTULO 13
Aplicações na Área de Assistência Médica
## R
> flu = fread("train.csv")
> flu[, flu.rate := as.numeric(TauxGrippe)]
> head(flu)
Id week region_code region_name TauxGrippe flu.rate
1: 3235 201352 42 ALSACE 7 7
2: 3236 201352 72 AQUITAINE 0 0
3: 3237 201352 83 AUVERGNE 88 88
4: 3238 201352 25 BASSE-NORMANDIE 15 15
5: 3239 201352 26 BOURGOGNE 0 0
6: 3240 201352 53 BRETAGNE 67 67
## R
> nrow(flu[is.na(flu.rate)]) / nrow(flu)
[1] 0.01393243
> unique(flu[is.na(flu.rate)]$region_name)
[1] "HAUTE-NORMANDIE" "NORD-PAS-DE-CALAIS" "PICARDIE"
[4] "LIMOUSIN" "FRANCHE-COMTE" "CENTRE"
[7] "AUVERGNE" "BASSE-NORMANDIE" "BOURGOGNE"
[10] "CHAMPAGNE-ARDENNE" "LANGUEDOC-ROUSSILLON" "PAYS-DE-LA-LOIRE"
[13] "CORSE"
## R
> flu[, year := as.numeric(substr(week, 1, 4))]
> flu[, wk := as.numeric(substr(week, 5, 6))]
> ## O estilo não é ótimo, temos colunas de 2 semanas.
## R
> flu[, date:= as.Date(paste0(as.character(flu$week), "1"), "%Y%U%u")]
115
## R
## Vamos focar Paris.
> paris.flu = flu[region_name == "ILE-DE-FRANCE"]
> paris.flu = paris.flu[order(date, decreasing = FALSE)]
## R
> paris.flu[, .N, year]
year N
1: 2004 53
2: 2005 52
...
9: 2012 52
10: 2013 52
## R
> paris.flu[, plot(date, flu.rate,
> type = "l", xlab = "Date",
> ylab = "Flu rate")]
## R
> paris.flu <- paris.flu[week != 53]
## R
> acf(paris.flu$flu.rate, )
> acf(diff(paris.flu$flu.rate, 52))
Figura 13-2. No primeiro gráfico, plotamos a função de autocorrelação para o índice da gripe
de Paris e, no segundo gráfico, plotamos o índice da gripe de Paris diferenciado. Analisamos
apenas um intervalo limitado de valores de lag.
## R
> plot(diff(diff(paris.flu$flu.rate, 52), 52))
> plot(diff(diff(paris.flu$flu.rate, 52), 1))
## R
> par(mfrow = c(2, 1))
> acf (diff(diff(paris.flu$flu.rate, 52), 1), lag.max = 104)
> pacf(diff(diff(paris.flu$flu.rate, 52), 1), lag.max = 104)
## R
> ## Ajuste do arima.
> ## Vamos estimar 2 semanas à frente.
> first.fit.size <- 104
> h <- 2
> n <- nrow(paris.flu) - h - first.fit.size
## R
> ylim <- range(paris.flu$flu.rate[300:400],
> fit.preds[, h][(300-h):(400-h)])
> par(mfrow = c(1, 1))
> plot(paris.flu$date[300:400], paris.flu$flu.rate[300:400],
> ylim = ylim, cex = 0.8,
> main = "Actual and predicted flu with SARIMA (2, 1, 0), (0, 1, 0)",
> xlab = "Date", ylab = "Flu rate")
> lines(paris.flu$date[300:400], fit.preds[, h][(300-h):(400-h)],
> col = 2, type = "l",
> lty = 2, lwd = 2)
## R
> ## Pré-aloca os vetores para manter coeficientes e ajustes.
> fit.preds <- array(0, dim = c(n, h))
> fit.coefs <- array(0, dim = c(n, 100))
>
> ## Regressores exógenos.
> ## que são componentes da série de Fourier ajustados aos dados
> flu.ts <- ts(log(paris.flu$flu.rate + 1) + 0.0001,
> frequency = 52)
> ## Adiciona pequenos deslocamentos,
> ## pois os valores O pequenos causam problemas numéricos no ajuste.
> exog.regressors <- fourier(flu.ts, K = 2)
> exog.colnames <- colnames(exog.regressors)
## R
> flu.ts = ts(log(paris.flu$flu.rate + 1) + 0.0001, ## add epsilon
> frequency = 52)
## R
> exog.regressors <- fourier(flu.ts, K = 2)
> exog.colnames <- colnames(exog.regressors)
## R
> fit <- auto.arima(data.to.fit,
> xreg = exogs.for.fit,
> seasonal = FALSE)
## R
> fit.preds[i - first.fit.size, ] <- forecast(fit, h = 2h,
> xreg = exogs.for.predict)$mean
## R
> ylim = range(paris.flu$flu.rate)
> plot(paris.flu$date[300:400], paris.flu$flu.rate[300:400],
> ylim = ylim, cex = 0.8,
> main = "Actual and predicted flu with ARIMA +
> harmonic regressors",
> xlab = "Date", ylab = "Flu rate")
> lines(paris.flu$date[300:400], exp(fit.preds[, h][(300-h):(400-h)]),
> col = 2, type = 'l',
> lty = 2, lwd = 2)
## R
> plot(test$flu.rate)
> which(test$flu.rate > 400)
Error in which(res$flu.rate > 400) : object 'res' not found
> which(test$flu.rate > 400)
[1] 1 59 108 109 110 111 112 113
> abline(v = 59)
> which.max(test$flu.rate)
[1] 109
> abline(v = 109)
## R
> files <- list.files(full.names = TRUE)
> files <- grep("entries", files, value = TRUE)
> dt <- data.table()
> for (f in files) {
> dt <- rbindlist(list(dt, fread(f)))
> }
>
> ## Remove o na das colunas
> dt <- dt[!is.na(sgv)]
## R
> dt[, timestamp := as.POSIXct(date)]
> ## Funciona para mim porque meu computador está no horário EST
> ## (horário da Costa Leste dos EUA), mas pode não funcionar para você.
>
> ## Forma adequada.
> dt[, timestamp := force_tz(as.POSIXct(date), "EST")]
>
> ## Ordem cronológica.
> dt = dt[order(timestamp, decreasing = FALSE)]
## R
> dt <- dt[!duplicated(dt)]
## R
> nrow(dt)
[1] 24861
> length(unique(dt$timestamp))
[1] 23273
> ## Ainda temos dados duplicados, tanto quanto timestamps,
> ## por isso os deletaremos.
## R
> ## Podemos identificar um exemplo usando dt[, .N, timestamp][order(N)]
> ## analisando a maioria dos dados repetidos.
> dt[date == "2015-03-10 06:27:19"]
device date dateString sgv direction type
1: dexcom 2015-03-10 06:27:19 Tue Mar 10 06:27:18 EDT 2015 66 Flat sgv
2: dexcom 2015-03-10 06:27:19 Tue Mar 10 06:27:18 EDT 2015 70 Flat sgv
3: dexcom 2015-03-10 06:27:19 Tue Mar 10 06:27:18 EDT 2015 66 Flat sgv
4: dexcom 2015-03-10 06:27:19 Tue Mar 10 06:27:18 EDT 2015 70 Flat sgv
5: dexcom 2015-03-10 06:27:19 Tue Mar 10 06:27:18 EDT 2015 66 Flat sgv
6: dexcom 2015-03-10 06:27:19 Tue Mar 10 06:27:18 EDT 2015 70 Flat sgv
7: dexcom 2015-03-10 06:27:19 Tue Mar 10 06:27:18 EDT 2015 66 Flat sgv
8: dexcom 2015-03-10 06:27:19 Tue Mar 10 06:27:18 EDT 2015 70 Flat sgv
## Um pouco mais de inspeção sugere que isso não é muito importante.
## R
> dt <- unique(dt, by=c("timestamp"))
## R
> ## Vamos dar uma olhada em algumas janelas de tempo mais curtas nos dados.
> start.date <- as.Date("2015-01-01")
> stop.date <- as.Date("2015-04-01")
## R
> ## Analise também um único dia para ver com o que se parecer
> ## Se tivéssemos mais tempo, provavelmente deveríamos fazer histogramas 2D de dias
> ## e ver como isso funciona.
> par(mfrow = c(2, 1))
> start.date = as.Date("2015-07-04")
> stop.date = as.Date("2015-07-05")
> dt[between(timestamp, start.date, stop.date),
> plot(timestamp, sgv, cex = .5)]
>
> start.date = as.Date("2015-07-05")
> stop.date = as.Date("2015-07-06")
> dt[between(timestamp, start.date, stop.date),
> plot(timestamp, sgv, cex = .5)]
> ## Se tivéssemos mais dias, poderíamos fazer algumas análises dos “day shapes",
> ## mas aqui parece que não temos dados suficientes.
## R
> ## Devemos considerar uma característica para o mês do ano.
> ## Talvez haja efeitos sazonais.
> dt[, month := strftime(timestamp, "%m")]
>
> dt[, local.hour := as.numeric(strftime(timestamp, "%H"))]
> ## Percebemos que algumas horas do dia estão excessivamente representadas
> ## porque essa pessoa não está usando o dispositivo de forma consistente
> ## ou talvez o dispositivo tenha problemas de registro ou funcionalidade.
> ## Como é um pico estranho à meia-noite,
> ## isso parece sugerir problemas com o dispositivo em vez do usuário
> ## (quem liga e desliga o dispositivo à meia-noite?).
>
> hist(dt$local.hour)
## R
> ## Os valores do índice glicêmico tendem a depender da hora do dia.
> plot(dt[, mean(sgv), local.hour][order(local.hour)], type = 'l',
> ylab = "Mean blood glucose per local hour",
> xlab = "Local hour")
Figura 13-13. A média do índice glicêmico no sangue varia bastante conforme a hora do dia.
## R
> dt[, nth.pos(direction, 2), local.hour][order(local.hour)]
local.hour V1
1: 0 FortyFiveUp
2: 1 FortyFiveUp
3: 2 FortyFiveUp
4: 3 FortyFiveDown
5: 4 FortyFiveDown
6: 5 FortyFiveUp
7: 6 FortyFiveDown
8: 7 FortyFiveDown
9: 8 FortyFiveDown
10: 9 FortyFiveUp
11: 10 FortyFiveUp
12: 11 FortyFiveDown
13: 12 FortyFiveDown
14: 13 FortyFiveDown
15: 14 FortyFiveDown
16: 15 FortyFiveDown
17: 16 FortyFiveUp
18: 17 FortyFiveUp
19: 18 FortyFiveDown
20: 19 FortyFiveUp
21: 20 FortyFiveUp
22: 21 FortyFiveUp
23: 22 FortyFiveUp
24: 23 FortyFiveDown
local.hour V1
## R
> ## PRIMEIRO, não faça isso da maneira errada!
> ## Nota: cuidado ao calcular as diferenças de tempo ingenuamente!
> as.numeric(dt$timestamp[10] - dt$timestamp[1])
[1] 40
> as.numeric(dt$timestamp[1000] - dt$timestamp[1])
[1] 11.69274
> dt$timestamp[1000] - dt$timestamp[1]
Time difference of 11.69274 days
## R
> dt[, sd.window := 0]
> for (i in 7:nrow(dt)) {
> dt[i, ]$sd.window = sd(dt[(i-6):i]$sgv)
> }
> ## Imputaremos os dados ausentes para os casos sd inválidos
> ## preenchendo a média da população (alerta de LOOKAHEAD, mas estamos cientes).
> imputed.val = mean(dt[valid.sd == TRUE]$sd.window)
> dt[valid.sd == FALSE, sd.window := imputed.val]
## R
> ## Agora também precisamos preencher nosso valor y,
> ## o alvo real.
> ## Isso também exigirá que verifiquemos a validade quando calculamos sd
> ## por causa dos dados atemporais contínuos no mesmo data.table.
> ## Vamos tentar prever 30 minutos à frente
> ## (previsões mais curtas são mais fáceis).
>
> ## udamos para 6 porque estamos amostrando a cada 5 minutos.
> dt[, pred.delta.t := as.numeric(difftime(shift(timestamp, 6,
> type = "lead"),
> timestamp,
> units = 'mins'))]
> dt[, pred.valid := !is.na(pred.delta.t) & pred.delta.t < 31]
>
> dt[, target := 0]
> for (i in 1:nrow(dt)) {
> dt[i, ]$target = dt[i + 6]$sgv
> }
## R
> ## Vamos dividir o dia em três partes, em vez de
> ## segmentos de 24 horas. Fazemos isso com base na
> ## noção de um dia "típico".
> dt[, day.q.1 := between(local.hour, 5, 10.99)]
> dt[, day.q.2 := between(local.hour, 11, 16.99)]
> dt[, day.q.3 := between(local.hour, 17, 22.99)]
> dt[, day.q.4 := !day.q.1 & !day.q.2 & !day.q.3]
## R
> ## Vamos definir um rótulo a "winter/not winter" em vez de
> ## um rótulo mensal. Esta decisão é parcialmente baseada na
> ## distribuição temporal de nossos dados.
> dt[, is.winter := as.numeric(month) < 4]
## R
> ## Precisamos também usar a codificação na características
> ## direction e limpar um pouco os dados.
> dt[direction == "NOT COMPUTABLE", direction := "NOT_COMPUTABLE"]
> dir.names = character()
> for (nm in unique(dt$direction)) {
> new.col = paste0("dir_", nm)
> dir.names = c(dir.names, new.col)
> dt[, eval(parse(text = paste0(new.col, " :=
(direction == '", nm, "')")))]
> }
## R
> model <- xgboost(data = as.matrix(train.X), label = train.Y,
> max.depth = 2,
> eta = 1,
> nthread = 2,
> nrounds = 250,
> objective = "reg:linear")
> y.pred <- predict(model, as.matrix(test.X))
## R
> ## Agora, vamos considerar um dia específico.
> test.data[date < as.Date("2015-03-17")]
> par(mfrow = c(1, 1))
> i <- 1
> j <- 102
> ylim <- range(test.Y[i:j], y.pred[i:j])
> plot(test.data$timestamp[i:j], test.Y[i:j], ylim = ylim)
> points(test.data$timestamp[i:j], y.pred[i:j], cex = .5, col = 2)
Figura 13-15. Analisar dias específicos de cada vez oferece uma perspectiva melhor sobre o
funcionamento do nosso algoritmo de predição.
Figura 13-16. Gráfico das predições e dos valores reais comparados individualmente em cada
extremidade. Na parte superior, representamos os valores comparados com os valores altos
dos índices glicêmicos medidos no sangue e, na parte inferior, comparamos os valores baixos.
## python
>>> df = pd.read_csv("sp500.csv")
>>> df.head()
>>> df.tail()
>>> df.index = df.Date
>>> df.Close.plot()
Figura 14-1. Dados brutos do início e do final do arquivo CSV. Observe que os valores
mudam visivelmente de 1990 a 2019 — nenhuma surpresa para quem conhece a história
dos mercados financeiros dos Estados Unidos.
137
Figura 14-2. A cotação diária de fechamento do S&P 500 não é uma série
temporal estacionária.
## python
>>> ## Escolhe três semanas (seg-sex) de anos diferentes.
>>> ## Escala a cotação de fechamento de cada dia pelas médias
>>> ## das cotações de fechamento da semana. 1990.
>>> vals = df['1990-05-07':'1990-05-11'].Close.values
>>> mean_val = np.mean(vals)
>>> plt.plot([1, 2, 3, 4, 5], vals/mean_val)
>>> plt.xticks([1, 2, 3, 4, 5],
>>> labels = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'])
>>>
>>> ## 2000
>>> vals = df['2000-05-08':'2000-05-12'].Close.values
>>> mean_val = np.mean(vals)
>>> plt.plot([1, 2, 3, 4, 5], vals/mean_val)
>>> plt.xticks([1, 2, 3, 4, 5],
>>> labels = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'])
>>>
>>> ## 2018
>>> vals = df['2018-05-07':'2018-05-11'].Close.values
>>> mean_val = np.mean(vals)
>>> plt.plot([1, 2, 3, 4, 5], vals/mean_val)
>>> plt.xticks([1, 2, 3, 4, 5],
>>> labels = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'])
## python
>>> df['Return'] = df.Close - df.Open
>>> df.Return.plot()
## python
>>> df['DailyVolatility'] = df.High - df.Low
>>> df.DailyVolatility.plot()
Figura 14-6. A volatilidade diária é uma série temporal de valor positivo (por definição)
que mostra graus de variância visivelmente diferentes em diferentes pontos da série
temporal S&P 500.
## python
>>> ewdf = df.ewm(halflife = 10).mean()
>>> ewdf.DailyVolatility.plot()
## python
>>> ## Calcula a variância móvel exponencialmente ponderada.
>>> vewdf = df.ewm(halflife = 10).var()
>>>
>>> ## Escalona penalizando e normalizando.
>>> scaled = df.DailyVolatility - ewdf.DailyVolatility
>>> scaled = scaled / vewdf.DailyVolatility**0.5
>>> scaled.plot()
## python
>>> df = df.dropna()
## python
>>> ## Divide nossos dados em componentes de treinamento e teste.
>>> train_df = df[:7000]
>>> test_df = df[7000:]
>>>
>>> ## Constrói nossas variáveis de pipeline a partir de dados de treinamento,
>>> ## tomando apenas valores de interesse de data frames maiores.
>>> horizon = 10
>>> X = train_df[:(7000 - horizon)][["ScaledVolatility", "ScaledReturn",
"ScaledVolume"]].values
>>> Y = train_df[horizon:]["ScaledReturn"].values
## python
>>> X = np.expand_dims(X, axis = 1)
## python
>>> X = np.split(X, X.shape[0]/10, axis = 0)
>>> X = np.concatenate(X, axis = 1)
>>> X.shape
(10, 699, 3)
## python
>>> ## Parâmetros de arquitetura.
>>> NUM_HIDDEN = 4
>>> NUM_LAYERS = 2
>>>
>>> ## Parâmetros de formatação de dados.
>>> BATCH_SIZE = 64
>>> WINDOW_SIZE = 20
>>>
>>> ## Parâmetros de treinamento.
>>> LEARNING_RATE = 1e-2
>>> EPOCHS = 30
## python
>>> Xinp = tf.placeholder(dtype = tf.float32,
>>> shape = [WINDOW_SIZE, None, 3])
>>> Yinp = tf.placeholder(dtype = tf.float32, shape = [None])
## python
>>> plt.plot(test_y_dict[MAX_EPOCH])
>>> plt.plot(test_y_hat_dict[MAX_EPOCH], 'r--')
>>> plt.show()
## python
>>> pearsonr(test_y_dict[MAX_EPOCH], test_y_hat_dict[MAX_EPOCH])
(0.03595786881773419, 0.20105107068949668)
## python
>>> plt.plot(test_y_dict[MAX_EPOCH][:100])
>>> plt.plot(test_y_hat_dict[MAX_EPOCH][:100] * 50, 'r--')
>>> plt.show()
Figura 14-10. Uma melhor noção de como as predições do modelo (linha tracejada) se
compara aos dados reais (linha contínua). No entanto, com uma correlação tão baixa,
talvez tendamos a ver um padrão que não existe. Por isso, as métricas quantitativas nos
ajudarão mais do que as avaliações visuais para dados financeiros ruidosos.
Figura 15-1. Dois dos quatro gráficos na página principal do site de dados abertos
de Singapura (acessado na primavera de 2019) são visualizações de séries temporais
utilizadas para mostrar informações importantes sobre o país.
147
## Linha de comando do Linux.
$ head 311.csv
## R
> df = fread("311.csv", skip = 0, nrows = 10)
> colnames(df)
[1] "Unique Key" "Created Date"
[3] "Closed Date" "Agency"
[5] "Agency Name" "Complaint Type"
[7] "Descriptor" "Location Type"
[9] "Incident Zip" "Incident Address"
[11] "Street Name" "Cross Street 1"
[13] "Cross Street 2" "Intersection Street 1"
[15] "Intersection Street 2" "Address Type"
[17] "City" "Landmark"
[19] "Facility Type" "Status"
[21] "Due Date" "Resolution Description"
[23] "Resolution Action Updated Date" "Community Board"
[25] "BBL" "Borough"
[27] "X Coordinate (State Plane)" "Y Coordinate (State Plane)"
[29] "Open Data Channel Type" "Park Facility Name"
[31] "Park Borough" "Vehicle Type"
[33] "Taxi Company Borough" "Taxi Pick Up Location"
[35] "Bridge Highway Name" "Bridge Highway Direction"
[37] "Road Ramp" "Bridge Highway Segment"
[39] "Latitude" "Longitude"
[41] "Location"
"Created Date"
"Closed Date"
"Due Date"
"Resolution Action Updated Date"
## R
> df$CreatedDate = df[, CreatedDate := as.POSIXct(CreatedDate,
> format = "%m/%d/%Y %I:%M:%S %p")
## R
> summary(as.numeric(df$ClosedDate - df$CreatedDate,
> units = "days"))
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
-75.958 1.000 4.631 13.128 12.994 469.737 113
Figura 15-2. Ainda que a maioria das datas nas primeiras 10 mil linhas de código
aparentemente se encaixem em 2014, elas também avançam até 2019 — e com
frequência!
## R
> ## Lê apenas as colunas de interesse.
> df = fread("311.tsv", select = c("Created Date", "Closed Date"))
>
> ## Usa o paradigma 'set' recomendado do data.table para definir os nomes das colunas.
> setnames(df, gsub(" ", "", colnames(df)))
>
> ## Elimina as linhas com um campo de data em branco.
> df = df[nchar(CreatedDate) > 1 & nchar(ClosedDate) > 1]
>
> ## Converte as colunas de string de data para POSIXct.
> fmt.str = "%m/%d/%Y %I:%M:%S %p"
> df[, CreatedDate := as.POSIXct(CreatedDate, format = fmt.str)]
> df[, ClosedDate := as.POSIXct(ClosedDate, format = fmt.str)]
>
> ## Ordenamento em ordem cronológica do CreatedDate.
> setorder(df, CreatedDate)
>
> ## Calcula o número de dias entre a criação e o encerramento
> ## da reclamação na linha direta 311.
> df[, LagTime := as.numeric(difftime(ClosedDate, CreatedDate,
> units = "days"))]
## python
>>> def next_obs(self, x):
>>> if self.num_obs < (self.b + 1):
>>> self.q.append(x)
>>> self.q.sort()
>>> self.num_obs = self.num_obs + 1
>>> else:
>>> self.next_obs2(x)
>>> self.next_obs = self.next_obs2
## python
>>> class PQuantile:
>>> ## INICIALIZAÇÃO.
>>> def __init__(self, b, discount_factor):
>>> self.num_obs = 0
>>> self.b = b
>>> self.discount_factor = discount_factor
>>> self.n = [i for i in range(self.b+1)]
>>> self.q = []
>>>
>>> ## DATA INTAKE
>>> def next_obs(self, x):
>>> if self.num_obs < (self.b + 1):
>>> self.q.append(x)
>>> self.q.sort()
>>> self.num_obs = self.num_obs + 1
## python
>>> qt = PQuantile(10, 0.3)
>>> qt_ests = []
>>>
>>> for _ in range(100):
>>> b.next_obs(uniform())
>>> if len(b.q) > 10:
>>> qt_ests.append(qt.q[4])
>>> for _ in range(100):
>>> b.next_obs(uniform(low = 0.9))
>>> qt_ests.append(qt.q[4])
>>>
>>> plt.plot(qt_ests)
Figura 15-6. Quando descontamos menos as medições mais antigas (multiplicando por
um fator de desconto maior), nossa estimativa de quantil demora mais para reconhecer
que a distribuição subjacente mudou.
## python
>>> import numpy
>>> nrows = 1000000
>>> qt_est1 = np.zeros(nrows)
>>> qt_est2 = np.zeros(nrows)
>>> qt_est3 = np.zeros(nrows)
>>> qt_est4 = np.zeros(nrows)
>>> qt_est5 = np.zeros(nrows)
>>> qt_est6 = np.zeros(nrows)
>>> qt_est7 = np.zeros(nrows)
>>> qt_est8 = np.zeros(nrows)
>>> qt_est9 = np.zeros(nrows)
>>> for idx, val in enumerate(df.LagTime[:nrows]):
>>> qt.next_obs(val)
>>> if len(qt.q) > 10:
>>> qt_est1[idx] = qt.q[1]
Figura 15-7. Os valores dos percentis 90, 70, 50, 30 e 20 ao longo do tempo para as
primeiras 100 mil linhas no conjunto de dados quando classificados por data de
encerramento.
## R
> ## Dividimos os dados de acordo com o que precisamos
> ## e fornecemos a eles os nomes de coluna esperados.
> df = df_wiki[, c("date", "views")]
> colnames(df) = c("ds", "y")
> ## Por último, plotamos um gráfico para ver como o pacote se saiu qualitativamente.
> plot(df$ds, df$y, col = 1, type = 'l', xlim = range(forecast$ds),
> main = "Actual and predicted Wikipedia pageviews of 'Facebook'")
> points(forecast$ds, forecast$yhat, type = 'l', col = 2)
Figura 16-1. Gráfico dos dados de contagem de páginas da Wikipédia (linha contínua) e
as previsões do Prophet para esses dados (linha tracejada espessa).
## R
> prophet_plot_components(m, forecast)
## R
> library(AnomalyDetection)
> data(raw_data)
> head(raw_data)
timestamp count
1 1980-09-25 14:01:00 182.478
2 1980-09-25 14:02:00 176.231
3 1980-09-25 14:03:00 183.917
4 1980-09-25 14:04:00 177.798
5 1980-09-25 14:05:00 165.469
6 1980-09-25 14:06:00 181.878
## R
> ## Detecta uma alta porcentagem de anomalias em qualquer direção.
> general_anoms = AnomalyDetectionTs(raw_data, max_anoms=0.05,
> direction='both')
>
> ## Detecta uma porcentagem menor de anomalias apenas na direção pos.
> high_anoms = AnomalyDetectionTs(raw_data, max_anoms=0.01,
> direction='pos')
Figura 16-4. As mesmas anomalias identificadas, agora com foco nas anomalias que
ocorreram em um cluster no mesmo dia.
163