R CMD check OK
[talweg.git] / reports / report.gj
1 -----
2 # Package R "talweg"
3
4 Le package $-$ Time-series sAmpLes forecasted With ExoGenous variables $-$ contient le
5 code permettant de (re)lancer les expériences numériques décrites dans cette partie et la
6 suivante. Les fonctions principales sont respectivement
7
8 * **getData()** pour construire un objet R contenant les données à partir de fichiers
9 CSV (extraits de bases de données). Le format choisi en R est une classe R6 (du package
10 du même nom) exposant en particulier les méthodes *getSerie(i)* et *getExo(i)* qui
11 renvoient respectivement la $i^{eme}$ série de 24h et les variables exogènes (mesurées)
12 correspondantes. Voir ?Data pour plus d'information, une fois le package chargé.
13 * **computeForecast()** pour calculer des prédictions sur une certaine plage temporelle
14 contenue dans *data <- getData(...)*
15 * **computeError()** pour évaluer les erreurs commises par différentes méthodes.
16
17 Le package contient en outre diverses fonctions graphiques *plotXXX()*, utilisées dans la
18 partie suivante.
19 -----r
20 # Chargement de la librairie (après compilation, "R CMD INSTALL .")
21 library(talweg)
22
23 # Acquisition des données (depuis les fichiers CSV)
24 ts_data <- read.csv(system.file("extdata","pm10_mesures_H_loc.csv",
25 package="talweg"))
26 exo_data <- read.csv(system.file("extdata","meteo_extra_noNAs.csv",
27 package="talweg"))
28 data <- getData(ts_data, exo_data, input_tz="GMT",
29 date_format="%d/%m/%Y %H:%M", working_tz="GMT",
30 predict_at=7, limit=120)
31 # Plus de détails à la section 1 ci-après.
32
33 # Prédiction de 10 courbes (jours 102 à 111)
34 pred <- computeForecast(data, 101:110, "Persistence", "Zero", memory=50,
35 horizon=12, ncores=1)
36 # Plus de détails à la section 2 ci-après.
37
38 # Calcul des erreurs (sur un horizon arbitraire <= horizon de prédiction)
39 err <- computeError(data, pred, horizon=6)
40 # Plus de détails à la section 3 ci-après.
41
42 # Puis voir ?plotError et les autres plot dans le paragraphe 'seealso'
43 -----
44 ${"##"} getData()
45
46 Les arguments de cette fonction sont, dans l'ordre :
47
48 1. **ts_data** : séries temporelles (fichier CSV avec entête ou data.frame) ; la
49 première colonne contient les heures, la seconde les valeurs.
50 2. **exo_data** : variables exogènes (fichier CSV avec entête ou data.frame) ; la
51 première colonne contient les jours, les $m$ suivantes les variables mesurées pour ce
52 jour, et les $m$ dernières les variables prédites pour ce même jour. Dans notre cas $m=4$
53 : pression, température, gradient de température, vitesse du vent.
54 3. **input_tz** : zone horaire pour ts_data (défaut : "GMT").
55 4. **date_format** : format des heures dans ts_data (défaut : "%d/%m/%Y %H:%M", format
56 du fichier transmis par Michel).
57 5. **working_tz** : zone horaire dans laquelle on souhaite travailler avec les données
58 (défaut : "GMT").
59 6. **predict_at** : heure à laquelle s'effectue la prévision $-$ et donc dernière heure
60 d'un bloc de 24h, relativement à working_tz. data`$`getSerie(3) renvoit ainsi les 24
61 valeurs de 8h à 7h pour le $3^{eme}$ bloc de 24h présent dans le jeu de données.
62 -----r
63 print(data)
64 #?Data
65 -----
66 ${"##"} computeForecast()
67
68 Les arguments de cette fonction sont, dans l'ordre :
69
70 1. **data** : le jeu de données renvoyé par getData()
71 2. **indices** : l'ensemble de jours dont on veut prévoir les "lendemains" (prochains
72 blocs de 24h) ; peut être donnée sous forme d'un vecteur de dates ou d'entiers
73 (correspondants aux numéros des jours).
74 3. **forecaster** : le nom du prédicteur principal à utiliser ; voir ?computeForecast
75 4. **pjump** : le nom du prédicteur de saut d'une série à l'autre ; voir
76 ?computeForecast
77 5. **memory** : le nombre de jours à prendre en compte dans le passé pour chaque
78 prévision (par défaut : Inf, c'est-à-dire tout l'historique pris en compte).
79 6. **horizon** : le nombre d'heures à prédire ; par défaut "data`$`getStdHorizon()",
80 c'est-à-dire le nombre d'heures restantes à partir de l'instant de prévision + 1 jusqu'à
81 minuit (17 pour predict_at=7 par exemple).
82 7. **ncores** : le nombre de processus parallèles (utiliser 1 pour une exécution
83 séquentielle)
84 -----r
85 print(pred)
86 #?computeForecast
87 -----
88 ${"##"} computeError()
89
90 Les arguments de cette fonction sont, dans l'ordre :
91
92 1. **data** : le jeu de données renvoyé par getData()
93 2. **pred** : les prédictions renvoyées par computeForecast()
94 3. **horizon** : le nombre d'heures à considérer pour le calcul de l'erreur ; doit être
95 inférieur ou égal à l'horizon utilisé pour la prédiction (même valeur par défaut :
96 "data`$`getStdHorizon()")
97 -----r
98 summary(err)
99 summary(err$abs)
100 summary(err$MAPE)
101 -----
102 ${"##"} Graphiques
103
104 Voir ?plotError : les autres fonctions graphiques sont dans la section 'seealso' :
105
106 ‘plotCurves’, ‘plotPredReal’, ‘plotSimils’, ‘plotFbox’,
107 ‘computeFilaments’, ‘plotFilamentsBox’, ‘plotRelVar’
108
109 ?plotXXX, etc.
110 ## $\clearpage$ How to do that?
111 -----
112 # Expérimentations
113
114 Cette partie montre les résultats obtenus via des variantes de l'algorithme décrit à la
115 section 2, en utilisant le package présenté à la section 3. Cet algorithme est
116 systématiquement comparé à deux approches naïves :
117
118 * la moyenne des lendemains des jours "similaires" dans tout le passé, c'est-à-dire
119 prédiction = moyenne de tous les mardis passé si le jour courant est un lundi par
120 exemple.
121 * la persistence, reproduisant le jour courant ou allant chercher le lendemain de la
122 dernière journée "similaire" (même principe que ci-dessus ; argument "same\_day").
123
124 Concernant l'algorithme principal à voisins, trois variantes sont étudiées dans cette
125 partie :
126
127 * avec simtype="mix" et raccordement "Neighbors" dans le cas "non local", i.e. on va
128 chercher des voisins n'importe où du moment qu'ils correspondent au premier élément d'un
129 couple de deux jours consécutifs sans valeurs manquantes.
130 * avec simtype="endo" + raccordement "Neighbors" puis simtype="none" + raccordement
131 "Zero" (sans ajustement) dans le cas "local" : voisins de même niveau de pollution et
132 même saison.
133
134 Pour chaque période retenue $-$ chauffage, épandage, semaine non polluée $-$ les erreurs
135 de prédiction sont d'abord affichées, puis quelques graphes de courbes réalisées/prévues
136 (sur le jour "en moyenne le plus facile" à gauche, et "en moyenne le plus difficile" à
137 droite). Ensuite plusieurs types de graphes apportant des précisions sur la nature et la
138 difficulté du problème viennent compléter ces premières courbes. Concernant les graphes
139 de filaments, la moitié gauche du graphe correspond aux jours similaires au jour courant,
140 tandis que la moitié droite affiche les lendemains : ce sont donc les voisinages tels
141 qu'utilisés dans l'algorithme.
142 <%
143 list_titles = ['Pollution par chauffage','Pollution par épandage','Semaine non polluée']
144 list_indices = ['indices_ch', 'indices_ep', 'indices_np']
145 %>
146 -----r
147 library(talweg)
148
149 P = ${P} #instant de prévision
150 H = ${H} #horizon (en heures)
151
152 ts_data = read.csv(system.file("extdata","pm10_mesures_H_loc_report.csv",
153 package="talweg"))
154 exo_data = read.csv(system.file("extdata","meteo_extra_noNAs.csv",
155 package="talweg"))
156 # NOTE: 'GMT' because DST gaps are filled and multiple values merged in
157 # above dataset. Prediction from P+1 to P+H included.
158 data = getData(ts_data, exo_data, input_tz = "GMT", working_tz="GMT",
159 predict_at=P)
160
161 indices_ch = seq(as.Date("2015-01-18"),as.Date("2015-01-24"),"days")
162 indices_ep = seq(as.Date("2015-03-15"),as.Date("2015-03-21"),"days")
163 indices_np = seq(as.Date("2015-04-26"),as.Date("2015-05-02"),"days")
164 % for i in range(3):
165 -----
166 ##<h2 style="color:blue;font-size:2em">${list_titles[i]}</h2>
167 ${"##"} ${list_titles[i]}
168 -----r
169 p1 = computeForecast(data, ${list_indices[i]}, "Neighbors", "Neighbors", horizon=H,
170 simtype="mix", local=FALSE)
171 p2 = computeForecast(data, ${list_indices[i]}, "Neighbors", "Neighbors", horizon=H,
172 simtype="endo", local=TRUE)
173 p3 = computeForecast(data, ${list_indices[i]}, "Neighbors", "Zero", horizon=H,
174 simtype="none", local=TRUE)
175 p4 = computeForecast(data, ${list_indices[i]}, "Average", "Zero", horizon=H)
176 p5 = computeForecast(data, ${list_indices[i]}, "Persistence", "Zero", horizon=H,
177 same_day=${'TRUE' if loop.index < 2 else 'FALSE'})
178 -----r
179 e1 = computeError(data, p1, H)
180 e2 = computeError(data, p2, H)
181 e3 = computeError(data, p3, H)
182 e4 = computeError(data, p4, H)
183 e5 = computeError(data, p5, H)
184 options(repr.plot.width=9, repr.plot.height=7)
185 plotError(list(e1, e5, e4, e2, e3), cols=c(1,2,colors()[258],4,6))
186
187 # noir: Neighbors non-local (p1), bleu: Neighbors local endo (p2),
188 # mauve: Neighbors local none (p3), vert: moyenne (p4),
189 # rouge: persistence (p5)
190
191 sum_p123 = e1$abs$indices + e2$abs$indices + e3$abs$indices
192 i_np = which.min(sum_p123) #indice de (veille de) jour "facile"
193 i_p = which.max(sum_p123) #indice de (veille de) jour "difficile"
194 -----
195 % if i == 0:
196 L'erreur absolue dépasse 20 sur 1 à 2 jours suivant les modèles (graphe en haut à
197 droite). C'est au-delà de ce que l'on aimerait voir (disons +/- 5 environ). Sur cet
198 exemple le modèle à voisins "contraint" (local=TRUE) utilisant des pondérations basées
199 sur les similarités de forme (simtype="endo") obtient en moyenne les meilleurs résultats,
200 avec un MAPE restant en général inférieur à 30% de 8h à 19h (7+1 à 7+12 : graphe en bas à
201 gauche).
202 % elif i == 1:
203 Il est difficile dans ce cas de déterminer une méthode meilleure que les autres : elles
204 donnent toutes de plutôt mauvais résultats, avec une erreur absolue moyennée sur la
205 journée dépassant presque toujours 15 (graphe en haut à droite).
206 % else:
207 Dans ce cas plus favorable les intensité des erreurs absolues ont clairement diminué :
208 elles restent souvent en dessous de 5. En revanche le MAPE moyen reste au-delà de 20%, et
209 même souvent plus de 30%. Comme dans le cas de l'épandage on constate une croissance
210 globale de la courbe journalière d'erreur absolue moyenne (en haut à gauche) ; ceci peut
211 être dû au fait que l'on ajuste le niveau du jour à prédire en le recollant sur la
212 dernière valeur observée.
213 % endif
214 -----r
215 options(repr.plot.width=9, repr.plot.height=4)
216 par(mfrow=c(1,2))
217
218 plotPredReal(data, p1, i_np); title(paste("PredReal p1 day",i_np))
219 plotPredReal(data, p1, i_p); title(paste("PredReal p1 day",i_p))
220
221 plotPredReal(data, p2, i_np); title(paste("PredReal p2 day",i_np))
222 plotPredReal(data, p2, i_p); title(paste("PredReal p2 day",i_p))
223
224 plotPredReal(data, p3, i_np); title(paste("PredReal p3 day",i_np))
225 plotPredReal(data, p3, i_p); title(paste("PredReal p3 day",i_p))
226
227 # Bleu : prévue ; noir : réalisée
228 -----
229 % if i == 0:
230 Le jour "facile à prévoir", à gauche, se décompose en deux modes : un léger vers 10h
231 (7+3), puis un beaucoup plus marqué vers 19h (7+12). Ces deux modes sont retrouvés par
232 les trois variantes de l'algorithme à voisins, bien que l'amplitude soit mal prédite.
233 Concernant le jour "difficile à prévoir" il y a deux pics en tout début et toute fin de
234 journée (à 9h et 23h), qui ne sont pas du tout anticipés par le programme ; la grande
235 amplitude de ces pics explique alors l'intensité de l'erreur observée.
236 % elif i == 1:
237 Dans le cas d'un jour "facile" à prédire $-$ à gauche $-$ la forme est plus ou moins
238 retrouvée, mais le niveau moyen est trop bas (courbe en bleu). Concernant le jour
239 "difficile" à droite, non seulement la forme n'est pas anticipée mais surtout le niveau
240 prédit est très inférieur au niveau de pollution observé. Comme on le voit ci-dessous
241 cela découle d'un manque de voisins au comportement similaire.
242 % else:
243 La forme est raisonnablement retrouvée pour les méthodes "locales", l'autre version
244 lissant trop les prédictions. Le biais reste cependant important, surtout en fin de
245 journée sur le jour "difficile".
246 % endif
247 -----r
248 par(mfrow=c(1,2))
249 f_np1 = computeFilaments(data, p1, i_np, plot=TRUE)
250 title(paste("Filaments p1 day",i_np))
251 f_p1 = computeFilaments(data, p1, i_p, plot=TRUE)
252 title(paste("Filaments p1 day",i_p))
253
254 f_np2 = computeFilaments(data, p2, i_np, plot=TRUE)
255 title(paste("Filaments p2 day",i_np))
256 f_p2 = computeFilaments(data, p2, i_p, plot=TRUE)
257 title(paste("Filaments p2 day",i_p))
258 -----
259 % if i == 0:
260 Les voisins du jour courant (période de 24h allant de 8h à 7h le lendemain) sont affichés
261 avec un trait d'autant plus sombre qu'ils sont proches. On constate dans le cas non
262 contraint (en haut) une grande variabilité des lendemains, très nette sur le graphe en
263 haut à droite. Ceci indique une faible corrélation entre la forme d'une courbe sur une
264 période de 24h et la forme sur les 24h suivantes ; **cette observation est la source des
265 difficultés rencontrées par l'algorithme sur ce jeu de données.**
266 % elif i == 1:
267 Les observations sont les mêmes qu'au paragraphe précédent : trop de variabilité des
268 lendemains (et même des voisins du jour courant).
269 % else:
270 Les graphes de filaments ont encore la même allure, avec une assez grande variabilité
271 observée. Cette observation est cependant trompeuse, comme l'indique plus bas le graphe
272 de variabilité relative.
273 % endif
274 -----r
275 par(mfrow=c(1,2))
276 plotFilamentsBox(data, f_np1); title(paste("FilBox p1 day",i_np))
277 plotFilamentsBox(data, f_p1); title(paste("FilBox p1 day",i_p))
278
279 # En pointillés la courbe du jour courant + lendemain (à prédire)
280 -----
281 % if i == 0:
282 Sur cette boxplot fonctionnelle (voir la fonction fboxplot() du package R "rainbow") l'on
283 constate essentiellement deux choses : le lendemain d'un voisin "normal" peut se révéler
284 être une courbe atypique, fort éloignée de ce que l'on souhaite prédire (courbes bleue et
285 rouge à gauche) ; et, dans le cas d'une courbe à prédire atypique (à droite) la plupart
286 des voisins sont trop éloignés de la forme à prédire et forcent ainsi un aplatissement de
287 la prédiction.
288 % elif i == 1:
289 On constate la présence d'un voisin au lendemain complètement atypique avec un pic en
290 début de journée (courbe en vert à gauche), et d'un autre phénomène semblable avec la
291 courbe rouge sur le graphe de droite. Ajouté au fait que le lendemain à prévoir est
292 lui-même un jour "hors norme", cela montre l'impossibilité de bien prévoir une courbe en
293 utilisant l'algorithme à voisins.
294 % else:
295 On peut réappliquer les mêmes remarques qu'auparavant sur les boxplots fonctionnels :
296 lendemains de voisins atypiques, courbe à prévoir elle-même légèrement "hors norme".
297 % endif
298 -----r
299 par(mfrow=c(1,2))
300 plotRelVar(data, f_np1); title(paste("StdDev p1 day",i_np))
301 plotRelVar(data, f_p1); title(paste("StdDev p1 day",i_p))
302
303 plotRelVar(data, f_np2); title(paste("StdDev p2 day",i_np))
304 plotRelVar(data, f_p2); title(paste("StdDev p2 day",i_p))
305
306 # Variabilité globale en rouge ; sur les voisins (+ lendemains) en noir
307 -----
308 % if i == 0:
309 Ces graphes viennent confirmer l'impression visuelle après observation des filaments. En
310 effet, la variabilité globale en rouge (écart-type heure par heure sur l'ensemble des
311 couples "aujourd'hui/lendemain"du passé) devrait rester nettement au-dessus de la
312 variabilité locale, calculée respectivement sur un voisinage d'une soixantaine de jours
313 (pour p1) et d'une dizaine de jours (pour p2). Or on constate que ce n'est pas du tout le
314 cas sur la période "lendemain", sauf en partie pour p2 le jour 4 $-$ mais ce n'est pas
315 suffisant.
316 % elif i == 1:
317 Comme précédemment les variabilités locales et globales sont confondues dans les parties
318 droites des graphes $-$ sauf pour la version "locale" sur le jour "facile"; mais cette
319 bonne propriété n'est pas suffisante si l'on ne trouve pas les bons poids à appliquer.
320 % else:
321 Cette fois la situation idéale est observée : la variabilité globale est nettement
322 au-dessus de la variabilité locale. Bien que cela ne suffise pas à obtenir de bonnes
323 prédictions de forme, on constate au moins l'amélioration dans la prédiction du niveau.
324 % endif
325 -----r
326 par(mfrow=c(1,2))
327 plotSimils(p1, i_np); title(paste("Weights p1 day",i_np))
328 plotSimils(p1, i_p); title(paste("Weights p1 day",i_p))
329
330 plotSimils(p2, i_np); title(paste("Weights p2 day",i_np))
331 plotSimils(p2, i_p); title(paste("Weights p2 day",i_p))
332 -----
333 % if i == 0:
334 Les poids se concentrent près de 0 dans le cas "non local" (p1), et se répartissent assez
335 uniformément dans [ 0, 0.2 ] dans le cas "local" (p2). C'est ce que l'on souhaite
336 observer pour éviter d'effectuer une simple moyenne.
337 % elif i == 1:
338 En comparaison avec le pragraphe précédent on retrouve le même (bon) comportement des
339 poids pour la version "non locale". En revanche la fenêtre optimisée est trop grande sur
340 le jour "facile" pour la méthode "locale" (voir affichage ci-dessous) : il en résulte des
341 poids tous semblables autour de 0.084, l'algorithme effectue donc une moyenne simple $-$
342 expliquant pourquoi les courbes mauve et bleue sont très proches sur le graphe d'erreurs.
343 % else:
344 Concernant les poids en revanche, deux cas a priori mauvais se cumulent :
345
346 * les poids dans le cas "non local" ne sont pas assez concentrés autour de 0, menant à
347 un lissage trop fort $-$ comme observé sur les graphes des courbes réalisées/prévues ;
348 * les poids dans le cas "local" sont trop semblables (à cause de la trop grande fenêtre
349 optimisée par validation croisée, cf. ci-dessous), résultant encore en une moyenne simple
350 $-$ mais sur moins de jours, plus proches du jour courant.
351 % endif
352 -----r
353 # Fenêtres sélectionnées dans ]0,7] :
354 # "non-local" 2 premières lignes, "local" ensuite
355 p1$getParams(i_np)$window
356 p1$getParams(i_p)$window
357
358 p2$getParams(i_np)$window
359 p2$getParams(i_p)$window
360 % endfor
361 -----
362 ${"##"} Bilan
363
364 Nos algorithmes à voisins ne sont pas adaptés à ce jeu de données où la forme varie
365 considérablement d'un jour à l'autre. Plus généralement cette décorrélation de forme rend
366 ardue la tâche de prévision pour toute autre méthode $-$ du moins, nous ne savons pas
367 comment procéder pour parvenir à une bonne précision.
368
369 Toutefois, un espoir reste permis par exemple en aggréger les courbes spatialement (sur
370 plusieurs stations situées dans la même agglomération ou dans une même zone).