-
Notifications
You must be signed in to change notification settings - Fork 0
/
06-AnalyseReseauEvenements.qmd
1525 lines (1294 loc) · 67.9 KB
/
06-AnalyseReseauEvenements.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Analyses d'évènements localisés sur un réseau {#sec-chap06}
Dans le [chapitre @sec-chap03], nous avons décrit des méthodes de répartition ponctuelle qui s'appliquent à des espaces classiques en deux dimensions, homogènes et sans limites dans toutes les directions. Par contre, lorsque les évènements (points) ne peuvent se produire que le long des lignes d'un réseau, les hypothèses de base des méthodes de répartition ponctuelle ne tiennent plus et produisent alors des résultats biaisés. Par conséquent, dans ce chapitre, nous abordons des méthodes d'analyse de répartition ponctuelle spécifiques à des évènements localisés sur un réseau.
::: bloc_package
::: bloc_package-header
::: bloc_package-icon
:::
**Liste des *packages* utilisés dans ce chapitre**
:::
::: bloc_package-body
- Pour importer et manipuler des fichiers géographiques :
- `sf` pour importer et manipuler des données vectorielles.
- `lubridate` pour manipuler des champs de format date.
- Pour construire des cartes et des graphiques :
- `tmap` pour construire des cartes thématiques.
- `ggplot2` pour construire des graphiques.
- `classInt` pour définir des intervalles sur une variable continue.
- `viridis` pour des palettes de couleurs.
- Pour les analyses de méthodes de répartition ponctuelle sur un réseau :
- `spNetwork` dédié aux analyses spatiales sur un réseau.
- `future` pour accélérer les calculs de `spNetwork`.
- `spdep` pour calculer des indices d'autocorrélation spatiale.
- `dbscan` pour l'algorithme DBSCAN.
:::
:::
## Pourquoi recourir à un réseau pour des méthodes d'analyse de répartition ponctuelle? {#sec-061}
Les méthodes classiques d'analyse de répartition ponctuelle postulent que l'espace analysé est le plus souvent en deux dimensions, homogène et sans limite dans toutes les directions. Toutefois, des phénomènes se produisent dans des espaces pour lesquels ces hypothèses sont totalement invalidées, menant à l'obtention de résultats biaisés, voire incohérents. C'est notamment le cas d'évènements localisés le long des segments d'un réseau qui sont par exemple :
- Des accidents de la route se produisent nécessairement le long des axes routiers.
- Des fuites d'eau se produisent le long des canalisations d'une ville.
- Des interruptions de service de bus se produisent le long de lignes de transport collectif.
- Certaines espèces d'oiseaux nichent systématiquement le long de cours d'eau.
Contrairement à un espace géographique classique en deux dimensions, il est possible de considérer qu'un réseau géographique (c'est-à-dire un réseau dont les nœuds et les lignes ont des coordonnées spatiales) est un espace en 1,5 dimension puisqu'il n'est possible de se déplacer que le long des lignes et de ne changer de direction qu'au niveau d'un nœud [@steenberghen2010spatial]. Cette distinction pose trois problèmes principaux si nous utilisons les méthodes de répartition ponctuelle classiques.
Premièrement, la distance euclidienne (à vol d'oiseau) tend systématiquement à sous-estimer la distance réelle entre deux points. En effet, la longueur d'un trajet sur un réseau est toujours plus grande ou égale à la distance euclidienne. La @fig-illusAnalyseReseauA illustre ce premier problème avec un cas simple comprenant trois points sur un réseau.
```{r}
#| echo: false
#| message: false
#| warning: false
library(sf)
library(spNetwork)
library(tmap)
library(dplyr)
## Définition d'une d'une simple situation
wkt_lines <- c(
"LINESTRING (0 5, 0 0)",
"LINESTRING (-5 0, 0 0)",
"LINESTRING (0 -5, 0 0)",
"LINESTRING (5 0, 0 0)")
linesdf <- data.frame(wkt = wkt_lines,
id = paste("l",1:length(wkt_lines),sep=""))
all_lines <- st_as_sf(linesdf, wkt = "wkt")
wkt_polygon <- "POLYGON ((-5 -4, 5 -4, 5 5, -5 5, -5 -4))"
polydf <- data.frame(wkt = wkt_polygon, id = 1)
polygons <- st_as_sf(polydf, wkt = "wkt")
## Définition de trois évènements
event <- data.frame(x=c(0,3,0),
y=c(3,0,-3),
id = c(1,2,3))
event <- st_as_sf(event, coords = c("x","y"))
## Représentation des distances réseau et euclidienne
wkt_lines <- c(
"LINESTRING (0 3, 0 0, 3 0)",
"LINESTRING (0 3, 3 0)")
linesdf <- data.frame(wkt = wkt_lines,
type = c('Réseau', 'Euclidienne'))
dists_df <- st_as_sf(linesdf, wkt = "wkt")
```
```{r}
#| echo: false
#| message: false
#| warning: false
#| label: fig-illusAnalyseReseauA
#| fig-align: center
#| fig-cap: "Problèmes générés par la non-prise en compte du réseau : sous-estimation des distances"
#| out-width: 85%
tm_shape(all_lines) +
tm_lines('black', lwd = 2) +
tm_shape(dists_df) +
tm_lines(col='type', title.col = "Type de distance", lwd = 4, palette = c('blue','green')) +
tm_shape(event) +
tm_dots('red', size = 1)+
tm_layout(frame = FALSE)
```
Deuxièmement, la non-prise en compte des lignes du réseau peut mener à analyser des secteurs dans lesquels les évènements ne peuvent pas se produire. La @fig-illusAnalyseReseauB illustre un cas où nous tenterions de produire un nouveau jeu de points distribués aléatoirement (points bleus), mais en respectant la densité initiale des points réels (points rouges). Ce type de méthode est notamment utilisé pour déterminer si un ensemble de points est plus ou moins concentré comparativement à ce que le hasard produirait. Comparer des points sur le réseau à des points hors du réseau conduit à surévaluer la concentration des points sur le réseau, car il y a plus d'espace en dehors du réseau que sur le réseau.
```{r}
#| echo: false
#| message: false
#| warning: false
#| label: fig-illusAnalyseReseauB
#| fig-align: center
#| fig-cap: "Problèmes générés par la non-prise en compte du réseau : surestimation de la concentration des points"
#| out-width: 85%
library(spatstat)
set.seed(1253)
points <- rpoispp(3/100, lmax=NULL, win=as.owin(c(-5,5,-5,5)),
nsim=1, drop=TRUE, ex=NULL,
forcewin=FALSE, warnwin=TRUE)
event_poiss_plan <- data.frame(
x=points$x,
y=points$y,
id = 1:length(points$x)
)
event_poiss_plan <- st_as_sf(event_poiss_plan, coords = c("x","y"))
map1 <- tm_shape(all_lines) +
tm_lines('black', lwd = 3) +
tm_shape(event) +
tm_dots('red', size = 1) +
tm_shape(event_poiss_plan) +
tm_dots('blue', size = 1) +
tm_layout(title = 'a. Échantillonnage planaire',
title.size = .9,
frame = FALSE)
set.seed(1235)
N2 <- sum(rpois(20, 3/20))
pts <- spNetwork::lines_points_along(all_lines, 0.01)
event_poiss_lin <- pts[sample(1:nrow(pts),size = N2),]
map2 <- tm_shape(all_lines) +
tm_lines('black', lwd = 3) +
tm_shape(event) +
tm_dots('red', size = 1) +
tm_shape(event_poiss_lin) +
tm_dots('blue', size = 1) +
tm_layout(title = 'b. Échantillonnage sur le réseau',
title.size = .9,
frame = FALSE)
tmap_arrange(map1, map2, nrow = 1)
```
Troisièmement, la masse des évènements ne se propage pas dans un espace en 2D comme dans un espace en 1,5D (@fig-illusAnalyseReseauC). Dans un réseau, la masse des évènements doit être divisée aux intersections. Si cette division n'est pas prise en compte, alors la masse des évènements est dupliquée aux intersections. Cette problématique se pose particulièrement aux méthodes d'estimation de densité par noyau que nous décrirons plus tard dans ce chapitre.
```{r}
#| echo: false
#| message: false
#| warning: false
#| label: fig-illusAnalyseReseauC
#| fig-align: center
#| fig-cap: "Problèmes générés par la non-prise en compte du réseau : la masse des évènements"
#| out-width: 85%
library(spatstat)
library(terra)
event_ppp <- as.ppp(st_coordinates(event), W = as.owin(c(-5,5,-5,5)))
titi <- terra::rast(density(event_ppp, sigma = 5 / 2, eps = 0.1))
crs(titi) <- "epsg:32188"
st_crs(all_lines) <- 32188
st_crs(event) <- 32188
map1 <- tm_shape(titi) +
tm_raster(style = 'cont', title = "") +
tm_shape(all_lines) +
tm_lines('black', lwd = 3) +
tm_shape(event) +
tm_dots('red', size = 1)+
tm_layout(main.title = 'a. Densité estimée sur espace planaire',
main.title.size = .9,
frame = FALSE,
legend.position = c("center", "top"),
legend.outside = TRUE,
legend.text.size = .8)
pt_samples <- lines_points_along(all_lines, 0.01)
nkde_dens <- nkde(
all_lines,
event,
w = rep(1, nrow(event)),
samples = pt_samples,
kernel_name = 'quartic',
bw = 5, method = 'continuous', verbose = FALSE
)
pt_samples$dens <- nkde_dens
map2 <- tm_shape(all_lines) +
tm_lines('black', lwd = 3) +
tm_shape(pt_samples) +
tm_dots('dens', title = "", size = 0.25, style = 'cont') +
tm_shape(event) +
tm_dots('red', size = 1) +
tm_layout(main.title = 'b. Densité estimée sur réseau',
main.title.size = .9,
frame = FALSE,
legend.outside = TRUE,
legend.position = c("center", "top"),
legend.text.size = .8)
tmap_arrange(map1, map2, nrow = 1)
```
De nombreuses méthodes d'analyse de répartition ponctuelle ont été adaptées pour pouvoir être appliquées avec des réseaux géographiques [@okabe2012spatial]. Dans ce chapitre, nous abordons les méthodes de densité par noyau sur un réseau (*network kernel density estimation*, NKDE) et la création de matrices de pondération spatiale avec des distances sur un réseau pour calculer des mesures d'autocorrélation spatiale.
## Cartographie de la densité d'évènements sur un réseau {#sec-062}
À la [section @sec-0342], nous avons décrit les méthodes d'analyse de densité dans une maille régulière : carte de chaleur ou estimation de la densité par noyau (*kernel density estimation* -- KDE). Pour un rappel, une KDE peut être utilisée pour tenter de reconstruire un processus spatial produisant des évènements. Le processus spatial en lui-même est impossible à mesurer, mais nous tentons de le reconstruire et de l'approximer en nous basant sur les évènements observés qui sont des réalisations du processus sous-jacent. Sur un réseau, la logique est exactement la même : un processus spatial qui est invisible conduit à la réalisation d'évènements le long des lignes d'un réseau géographique.
### Estimation de la densité des points sur un réseau {#sec-0621}
L'estimation de la densité sur un réseau (*Network Kernel Density Estimation* -- NKDE) utilise une approche similaire à la KDE ([section @sec-0342]). L'idée générale est de répartir la masse des évènements le long des lignes du réseau autour de chaque évènement et d'additionner ensuite ces densités pour obtenir une estimation locale de l'intensité du processus spatial générant ces évènements. Comparativement à la KDE, les spécificités de la NKDE sont les suivantes :
- Les intensités sont calculées non pas sur des pixels, mais sur leurs équivalents appelés **lixels**. Un lixel correspond à simplement une portion de segment de ligne du réseau d'une longueur déterminée (50 mètres par exemple).
- Les distances sont calculées sur le réseau et non à vol d'oiseau.
Comme pour la KDE, il faut déterminer la valeur du rayon d'influence (*bandwidth*) et choisir une fonction *kernel*.
#### Trois formes de NKDE {#sec-06211}
La NKDE peut prendre trois formes différentes traitant différemment la répartition de la masse aux intersections dans un réseau.
```{r}
#| echo: false
#| message: true
#| warning: false
library(rgl)
knitr::knit_hooks$set(webgl = hook_webgl)
```
```{r}
#| echo: false
#| message: true
#| warning: false
source('code_complementaire/code_3d_NKDE.R')
library(spNetwork)
library(sf)
library(rgl)
library(tmap)
library(dbscan)
## we define a set of lines
wkt_lines <- c(
"LINESTRING (0 0, 1 0)", "LINESTRING (1 0, 2 0)", "LINESTRING (2 0, 3 0)",
"LINESTRING (0 1, 1 1)", "LINESTRING (1 1, 2 1)", "LINESTRING (2 1, 3 1)",
"LINESTRING (0 2, 1 2)", "LINESTRING (1 2, 2 2)", "LINESTRING (2 2, 3 2)",
"LINESTRING (0 3, 1 3)", "LINESTRING (1 3, 2 3)", "LINESTRING (2 3, 3 3)",
"LINESTRING (0 0, 0 1)", "LINESTRING (0 1, 0 2)", "LINESTRING (0 2, 0 3)",
"LINESTRING (1 0, 1 1)", "LINESTRING (1 1, 1 2)", "LINESTRING (1 2, 1 3)",
"LINESTRING (2 0, 2 1)", "LINESTRING (2 1, 2 2)", "LINESTRING (2 2, 2 3)",
"LINESTRING (3 0, 3 1)", "LINESTRING (3 1, 3 2)", "LINESTRING (3 2, 3 3)"
)
## and create a spatial lines dataframe
linesdf <- data.frame(wkt = wkt_lines,
id = paste("l",1:length(wkt_lines),sep=""))
all_lines <- st_as_sf(linesdf, wkt = "wkt")
## we define now one event
event <- data.frame(x=c(1),
y=c(1.5))
event <- st_as_sf(event, coords = c("x","y"))
samples_pts <- lines_points_along(all_lines,0.01)
simple_kernel <- nkde(all_lines, event, w = 1,
samples = samples_pts, kernel_name = "quartic",
bw = 2, method = "simple", div = "bw",
digits = 3, tol = 0.001, grid_shape = c(1,1),
check = FALSE,
verbose = FALSE)
discontinuous_kernel <- nkde(all_lines, event, w = 1,
samples = samples_pts, kernel_name = "quartic",
bw = 2, method = "discontinuous", div = "bw",
digits = 3, tol = 0.001, grid_shape = c(1,1),
check = FALSE,
verbose = FALSE)
continuous_kernel <- nkde(all_lines, event, w = 1,
samples = samples_pts, kernel_name = "quartic",
bw = 2, method = "continuous", div = "bw",
digits = 3, tol = 0.001, grid_shape = c(1,1),
check = FALSE,
verbose = FALSE)
```
**NKDE géographique (Geo-NKDE)**. Il s'agit du cas le plus simple, car aucun traitement particulier n'est réalisé aux intersections du réseau et la densité d'un lixel est simplement basée sur la distance entre le centre de ce lixel et un évènement. Cette méthode est intuitive et peu coûteuse en temps de calcul, mais elle produit des résultats biaisés. En effet, si la masse d'un évènement se propage de façon continue dans toutes les directions à une intersection, alors la masse est multipliée par le nombre de directions possibles. Par exemple, à la @fig-GeoNKDEMasseBiais, qui comprend une intersection avec trois segments connectés, la masse totale est égale à 150 % et non à 100 %. Ainsi, dans un réseau avec de nombreuses intersections, l'intensité est systématiquement surestimée avec cette méthode.
![Répartition de la masse avec une NKDE géographique](images/Chap06/GeoNKDEMasse.png){#fig-GeoNKDEMasseBiais width="50%" fig-align="center"}
Un aperçu en 3D de la répartition de la masse est disponible à la figure ci-dessous avec un seul évènement.
```{r}
#| echo: false
#| message: false
#| warning: false
#| eval: true
#| webgl: true
#| label: fig-GeoNKDE
#| fig-align: center
#| fig-cap: "Visualisation du Geo-NKDE"
#| out-width: 100%
d3_plot_situation(all_lines, event, samples_pts, simple_kernel, scales = c(1,1,3))
```
La formule pour calculer la Geo-NKDE est :
$$
\hat{\lambda}_h(u)=\frac{1}{h} \sum_{i=1}^N k\left(\frac{\operatorname{dist}_{\text {net }}\left(u, e_i\right)}{h}\right) \text{ avec :}
$$ {#eq-GeoNKDEeq}
- $\hat{\lambda}_h(u)$, l'estimation de l'intensité au point $u$ avec la *bandwidth* $h$.
- $N$, le nombre d'évènements.
- $k$, une fonction *kernel*.
- $\operatorname{dist}_{\text{net}}\left(u, e_i\right)$, la distance réseau entre la localisation $u$ et l'évènement $e_i$.
**NKDE discontinue (ESD-NKDE)**. Cette seconde méthode impose que la masse des évènements soit divisée aux intersections par le nombre de directions possible. En procédant ainsi, il est possible d'éviter le biais de la Geo-NKDE, mais cela conduit à une estimation discontinue de la NKDE. En effet, l'intensité d'un évènement chute fortement au détour d'une intersection ce qui est contre-intuitif en géographie bien que l'intensité produite soit non biaisée (voir la figure ci-dessous). Comme pour la précédente, le second avantage de cette méthode est qu'elle est peu coûteuse en temps de calcul.
```{r}
#| echo: false
#| message: false
#| warning: false
#| eval: true
#| webgl: true
#| label: fig-ESDNKDE
#| fig-align: center
#| fig-cap: Visualisation du Geo-NKDE
#| out-width: 100%
d3_plot_situation(all_lines, event, samples_pts, discontinuous_kernel, scales = c(1,1,3))
```
La formule pour calculer la ESD-NKDE est la suivante :
$$
\hat{\lambda}_h\left(u, e_i\right)=k\left(d_{i s t_{n e t}}\left(u, e_i\right)\right) \prod_{j=1}^J\left(\frac{1}{\left(n_{i j}-1\right)}\right) \text{ avec :}
$$ {#eq-ESDNKDEeq}
- $\prod_{j=1}^J\left(\frac{1}{\left(n_{i j}-1\right)}\right)$ est le terme qui permet de contrôler la réduction de la masse due aux $J$ intersections rencontrées entre $u$ et $e_i$ et ayant un nombre d'embranchements $n_{ij}$.
**NKDE continue (ESC-NKDE)**. La troisième méthode implique également de diviser la masse des évènements aux intersections, mais aussi de corriger rétroactivement la masse précédant l'intersection pour forcer l'estimation à être continue. Cette estimation est donc non biaisée et ne produit pas de discontinuité (voir la figure ci-dessous). Cependant, la correction rétroactive nécessite un temps de calcul nettement plus long que les deux précédentes méthodes.
```{r}
#| echo: false
#| message: false
#| warning: false
#| eval: true
#| webgl: true
#| label: fig-ESCNKDE
#| fig-align: center
#| fig-cap: Visualisation du Geo-NKDE
#| out-width: 100%
d3_plot_situation(all_lines, event, samples_pts, continuous_kernel, scales = c(1,1,3))
```
Du fait de sa nature récursive, il est difficile de présenter la ESC-NKDE avec une équation. Cependant, l'algorithme complet est décrit par Atsuyuki Okabe et Sugihara Kokichi [-@okabe2012spatial].
La @fig-ComparaisonMasseNKDE permet de comparer la répartition de la masse des évènements aux intersections entre les NKDE, tandis que le @tbl-TabTroisNKDE résume les avantages et inconvénients des trois types de NKDE.
![Comparaison des trois types de NDKDE : géographique (Geo-NKDE), discontinue (ESD-NKDE) et continue (ESC-NKDE)](images/Chap06/Comparaison3NKDE.jpg){#fig-ComparaisonMasseNKDE width="100%" fig-align="center"}
```{r}
#| label: tbl-TabTroisNKDE
#| tbl-cap: Comparaison des trois NKDE
#| echo: false
#| message: false
#| warning: false
df1 <- data.frame(
Matrice = c("NKDE géographique (Geo-NKDE)",
"NKDE discontinue (ESD-NKDE)",
"NKDE continue (ESC-NKDE)"),
Av = c("Intuitive et facile à calculer",
"Respect de la masse totale et facile à calculer",
"Respect de la masse totale et intuitive"),
Des = c("La somme de la masse totale est inexacte.",
"L’espace discontinu est contre-intuitif",
"Couteux en termes de temps de calcul")
)
knitr::kable(df1,
format.args = list(decimal.mark = ',', big.mark = " "),
col.names = c("NKDE", "Avantage", "Désavantage"),
align=c("l", "l", "l"),
format = "markdown")
```
::: bloc_aller_loin
::: bloc_aller_loin-header
::: bloc_aller_loin-icon
:::
***Package*** **`spNetwork`**
:::
::: bloc_aller_loin-body
Pour une lecture plus détaillée sur les trois NKDE, nous vous recommandons de lire l'article décrivant le *package* `spNetwork` [@RJ-2021-102].
:::
:::
#### Correction de Diggle {#sec-06212}
Très souvent, les données collectées pour un phénomène analysé sont limitées à une zone géographique (territoire d'étude) et les évènements se produisant en dehors de cette zone ne sont pas enregistrés. Ce biais de collecte entraîne une sous-estimation systématique de l'intensité estimée par les méthodes KDE et NKDE aux frontières de la zone d'étude. Pour limiter cette sous-estimation, il est préférable de collecter directement les données dans un périmètre plus large que la zone étudiée. Cependant, lorsque les données ont déjà été collectées, il est possible d'appliquer la correction de Diggle [-@diggle1985kernel] (@eq-DigglesCor).
$$
\begin{gathered}
\lambda^D(u)=\frac{1}{bw} \sum_{i=1}^n w_i \cdot \frac{1}{e\left(e_i\right)} K\left(\operatorname{dist}\left(u, e_i\right)\right) \\
e(u)=\int_W^v K(\operatorname{dist}(u, v)) \text{ avec :}
\end{gathered}
$$ {#eq-DigglesCor}
- $e(u)$ étant la masse de l'évènement $u$ localisé dans la zone d'étude $W$.
Concrètement, cette correction propose d'augmenter la masse des évènements localisés à proximité de la frontière de la zone d'étude. Ces évènements voient leur pondération multipliée par l'inverse de la proportion de leur masse comprise à l'intérieur de la zone d'étude. Ainsi, un point dont 100 % de la masse se trouve dans la zone d'étude garde le même poids (1/1 = 1); un point avec 75 % de sa masse dans la zone d'étude a son poids multiplié par 4/3 (1/0,75 = 4/3) et un point avec 50 % de sa masse dans la zone d'étude a son poids multiplié par deux (1/0,5 = 2).
#### *Bandwidths* adaptatives {#sec-06213}
Jusqu'ici, nous avons présenté les méthodes d'estimation de la densité par *kernel* avec des *bandwidths* globales. Il existe une catégorie de *kernels* appelés adaptatifs qui utilisent, comme leur nom l'indique, des *bandwidths* s'adaptant localement.
L'idée générale est que chaque évènement peut avoir sa propre *bandwidth* locale. Cette modification se justifie du point de vue théorique. Pour un rappel, nous considérons que les évènements ont eu lieu à un certain endroit du fait d'un patron spatial d'arrière-plan. Nous formulons donc l'hypothèse qu'un évènement aurait pu se produire dans un certain rayon (*bandwidth*) autour de son emplacement réel selon une probabilité décroissante avec la distance (fonction *kernel*). Dans les secteurs où se situent de nombreux points, notre incertitude sur la localisation d'un point est moins grande, nous pouvons donc utiliser des *bandwidths* plus petites. Cependant, dans les secteurs avec très peu de points, le processus spatial est beaucoup plus diffus et l'évènement aurait pu se produire dans un rayon plus large, incitant à utiliser des *bandwidths* plus grandes.
Deux approches sont le plus souvent utilisées pour créer des *bandwidths* locales : la méthode d'Abramson [-@abramson1982bandwidth] et la méthode des *k* plus proches voisins [@orava2011k].
##### *Bandwidths* adaptatives par la méthode d'Abramson {#sec-062131}
La méthode d'Abramson [-@abramson1982bandwidth] propose d'utiliser des *bandwidths* locales qui sont inversement proportionnelles à la racine carrée de l'intensité locale du processus spatial étudié. Cependant, puisque nous ne disposons pas d'une mesure de ce processus, nous devons en fournir une approximation à priori. Cette approximation est obtenue en sélectionnant une première *bandwidth* globale (appelée pilote) et l'estimation de la densité est effectuée. Une fois que la densité à priori est calculée à la localisation exacte de chaque évènement, il est ensuite possible de calculer une *bandwidth* locale pour chaque évènement avec l'@eq-AbramsonBW :
$$
h(e_{i}) = h_{0} \times \frac{1}{\sqrt{\tilde{f}h_{0}(e_{i})}} \times \frac{1}{\gamma_{f}}\\
\gamma_{f} = \exp(\frac{\sum_{i}log(\frac{1}{\sqrt{\tilde{f}h_{0}(e_{i})}})}{n}) \text{ avec :}
$$ {#eq-AbramsonBW}
- $h(e_{i})$ est la *bandwidth* locale pour l'évènement $e_{i}$.
- $h_0$ est la *bandwidth* pilote globale.
- $\tilde{f}h_{0}(e_{i})$ est l'estimation locale de la densité à priori pour l'évènement $e_{i}$ avec $h_0$.
L'objectif est bien évidemment de sélectionner la *bandwidth* pilote $h_0$.
##### *Bandwidths* adaptatives par la méthode des *k* plus proches voisins {#sec-062132}
Cette méthode est certainement plus facile à expliquer. Elle consiste à calculer, pour chaque évènement, la distance qui le sépare de son plus proche voisin de rang *k* (*k* étant un entier plus grand que 0). Dans des secteurs avec peu d'évènements, la distance au plus proche voisin de rang *k* sera plus grande. L'enjeu pour cette méthode est donc de déterminer *k*. La @fig-ESCNKDE2 illustre l'impact de ces méthodes sur les *bandwidths* locales en prenant le jeu de données fourni dans le *package* `spNetwork`, portant sur les accidents à vélo dans les quartiers centraux de Montréal.
```{r}
#| echo: false
#| message: false
#| warning: false
#| label: fig-ESCNKDE2
#| fig-align: center
#| fig-cap: Comparaison des deux principales méthodes de création de *bandwidths* locales
#| out-width: 100%
library(spNetwork)
library(dbscan)
library(sf)
library(tmap)
data(mtl_network)
data(bike_accidents)
lixels <- lixelize_lines(mtl_network, 200, mindist = 50)
samples <- lines_center(lixels)
## on trouve les bandwidths locales par la méthode d'Abramson
adapt_densities <- nkde(mtl_network,
events = bike_accidents,
w = rep(1,nrow(bike_accidents)),
samples = samples,
kernel_name = "quartic",
bw = 300, div= "bw",
adaptive = TRUE, # we use here an adaptive bandwidth
trim_bw = 600, # the maximum local values of bandwidth will be 600m
method = "discontinuous", digits = 1, tol = 1,
grid_shape = c(1,1), max_depth = 16,
agg = 5, #we aggregate events within a 5m radius (faster calculation)
sparse = TRUE,
verbose=FALSE)
circles1 <- st_buffer(adapt_densities$events,
dist = adapt_densities$events$bw)
## on trouve les bandwidths locales par la méthode des K plus proches voisins
net_dists <- spNetwork::network_knn(adapt_densities$events,
lines = mtl_network,
k = 10, verbose = FALSE, digits = 1)
dist_k7 <- net_dists$distances[,5]
circles2 <- st_buffer(adapt_densities$events,
dist = dist_k7)
ids <- c(1,52,20,86,14,75,126,200,177)
map1 <- tm_shape(mtl_network) +
tm_lines("black") +
tm_shape(adapt_densities$events) +
tm_dots("red", size = 0.2) +
tm_shape(circles1[ids,]) +
tm_borders("blue", lwd = 2) +
tm_layout(main.title = "Méthode d'Abramson (h0 = 300 mètres)",
main.title.size = .9,
frame = FALSE)
map2 <- tm_shape(mtl_network) +
tm_lines("black") +
tm_shape(adapt_densities$events) +
tm_dots("red", size = 0.2) +
tm_shape(circles2[ids,]) +
tm_borders("blue", lwd = 2)+
tm_layout(main.title = "Méthode des plus proches voisins (k = 5)",
main.title.size = .9,
frame = FALSE)
tmap_arrange(map1, map2, nrow = 1)
```
#### Sélection d'une *bandwidth* {#sec-06214}
Comme signalé dans la [section @sec-03421], le choix de la *bandwidth* est crucial dans l'application d'une estimation de densité par *kernel*. Dans le cas de la NKDE, le nombre de méthodes est plus limité que pour la KDE, mais il est toujours possible d'utiliser l'approche par validation croisée des probabilités (*likelihood cross validation*). Plus exactement, cette méthode choisit une *bandwidth* de façon à minimiser l'impact qu'aurait le fait de retirer un évènement du jeu de donnée (*leave one out cross validation*). En effet, puisque chaque point est une réalisation d'un processus spatial, le fait de retirer un point des données ne devrait affecter que marginalement l'estimation locale de l'intensité du processus spatial. Il est possible de minimiser ce score en sélectionnant la bonne *bandwidth*. Il est possible de calculer ce score pour une *bandwidth* donnée avec l'@eq-LOOLCV :
$$
\operatorname{LCV}(bw)=\sum_i \log \hat{\lambda}_{-i}\left(x_i\right)
\text{ avec :}
$$ {#eq-LOOLCV}
- $bw$ est la *bandwidth* à évaluer.
- $\hat{\lambda}_{-i}$ est l'intensité estimée à la localisation de l'évènement *i* sans la présence de l'évènement *i*.
Notez qu'il s'agit d'une simplification de l'équation qui comporte normalement un second terme qui tend à être une constante et peut donc être retiré pour alléger les calculs [@loader2006local]. Cette méthode peut aussi être utilisée pour sélectionner la *bandwidth* pilote ou *k* lorsque nous utilisons une *bandwidth* adaptative.
### Mise en œuvre dans R {#sec-0612}
Nous analysons ici les collisions ayant eu lieu sur le réseau routier de la ville de Sherbrooke. Nous commençons par appliquer une NKDE avec *bandwidth* fixe et nous la comparons avec deux NKDE utilisant des *bandwidths* adaptatives. Nous utilisons principalement le *package* `spNetwork` [@RJ-2021-102].
La première étape consiste à charger les données des accidents et le réseau routier. La @fig-situationNKDE permet de visualiser la répartition spatiale des accidents.
```{r}
#| echo: false
#| message: true
#| warning: false
# save(intensity, intensity_adpt, intensity_adpt_knn, eval_bandwidth,
# eval_bandwidth_adapt, eval_bandwidth_knearest,
# lixels, lixels_centers,
# file = 'data/chap06/pre_calculated_results.rda')
load('data/chap06/pre_calculated_results.rda')
```
```{r}
#| echo: true
#| message: false
#| warning: false
#| label: fig-situationNKDE
#| fig-align: center
#| fig-cap: Accidents sur le réseau routier de la ville de Sherbrooke
#| out-width: 85%
library(sf)
library(tmap)
library(ggplot2)
library(spNetwork)
library(future) # package utilisé pour accélérer les calculs dans spNetwork
future::plan(future::multisession(workers = 5))
## Importation des couches géographiques
routes <- st_read('data/chap01/shp/Segments_de_rue.shp', quiet = TRUE)
collisions <- st_read('data/chap04/DataAccidentsSherb.shp', quiet = TRUE)
## Application de la même projection
routes <- st_transform(routes, 2949)
routes <- sf::st_cast(routes, 'LINESTRING')
collisions <- st_transform(collisions, 2949)
## Cartographie des données des collisions et du réseau routier
tm_shape(routes) +
tm_lines('grey20') +
tm_shape(collisions) +
tm_dots('red', size = 0.05)+
tm_layout(frame = FALSE)
```
Pour l'analyse, nous utilisons la fonction *kernel* quadratique et la NKDE continue (ESC-NKDE). Puis, nous choisissons une *bandwidth* avec l'approche par validation croisée des probabilités. Notez que pour réduire le temps de calcul, la NKDE discontinue (ESD-NKDE) est utilisée dans la phase de sélection de la *bandwidth*, car il est bien plus rapide à calculer.
```{r}
#| eval: false
eval_bandwidth <- bw_cv_likelihood_calc.mc(
bws = seq(100, 1200, 50),
lines = routes,
events = collisions,
w = rep(1, nrow(collisions)), # le poids de chaque évènement est 1
kernel_name = 'quartic',
method = 'discontinuous',
adaptive = FALSE,
max_depth = 10,
digits = 1,
tol = 0.1,
agg = 5, # les accidents dans un rayon de 5 mètres seront agrégés
grid_shape = c(5,5),
verbose = TRUE)
```
À la @fig-bwScoresNKDE, nous constatons qu'au-delà de 900 mètres, le gain obtenu en augmentant la valeur de la *bandwidth* est marginal. Par conséquent, nous retenons cette valeur de *bandwidth* pour la première estimation de l'intensité des accidents.
```{r}
#| echo: true
#| message: false
#| warning: false
#| label: fig-bwScoresNKDE
#| fig-align: center
#| fig-cap: Scores des *bandwidths* globales
#| out-width: 75%
## Graphique pour les bandwidths
ggplot(eval_bandwidth) +
geom_path(aes(x = bw, y = cv_scores)) +
geom_point(aes(x = bw, y = cv_scores), color = 'red')+
labs(x = "Valeur de la bandwidth", y = "Valeur du CV")
```
```{r}
#| eval: false
#| echo: true
#| message: false
#| warning: false
## Création des lixels d'une longueur de 100 mètres
lixels <- lixelize_lines(routes, 100, mindist = 50)
## Centroïdes des lixels
lixels_centers <- spNetwork::lines_center(lixels)
```
```{r}
#| eval: false
## Calcul de la NKDE
future::plan(future::multisession(workers=2))
intensity <- nkde.mc(lines = routes,
events = collisions,
w = rep(1, nrow(collisions)),
samples = lixels_centers,
kernel_name = 'quartic',
bw = 900,
adaptive = FALSE,
method = 'continuous',
max_depth = 8,
digits = 1,
tol = 0.1,
agg = 5,
verbose = FALSE,
grid_shape = c(5,5))
if (!inherits(future::plan(), "sequential")) future::plan(future::sequential)
```
Une fois que les valeurs de densité sont obtenues, nous pouvons les cartographier à l'échelle des lixels (@fig-mapNKDE).
```{r}
#| echo: true
#| message: false
#| warning: false
#| label: fig-mapNKDE
#| fig-align: center
#| fig-cap: Densité des accidents sur le réseau routier de Sherbrooke
#| out-width: 100%
lixels$density <- intensity * 1000
tm_shape(lixels) +
tm_lines("density", lwd = 1.5, n = 7, style = "fisher",
legend.format = list(text.separator = "à"))+
tm_layout(frame=FALSE)
```
Nous pouvons à présent utiliser une *bandwidth* adaptative. Pour cela, nous devons réévaluer les différentes *bandwidths* globales avec l'approche par validation croisée des probabilités.
```{r}
#| eval: false
future::plan(future::multisession(workers=2))
eval_bandwidth_adapt <- bw_cv_likelihood_calc.mc(
bws = seq(100, 1200, 50),
lines = routes,
events = collisions,
w = rep(1, nrow(collisions)), # le poids de chaque évènement sera 1
kernel_name = 'quartic',
method = 'discontinuous',
adaptive = TRUE,
trim_bws = seq(100, 1200, 50) * 2,
max_depth = 10,
digits = 2,
tol = 0.1,
agg = 5, # tous les accidents dans un rayon de 5m seront agrégés
grid_shape = c(5,5),
verbose = TRUE
)
if (!inherits(future::plan(), "sequential")) future::plan(future::sequential)
```
```{r}
#| echo: true
#| message: false
#| warning: false
#| label: fig-bwScoresNKDEadpt
#| fig-align: center
#| fig-cap: Scores des bandwidths adaptatives
#| out-width: 65%
ggplot() +
geom_path(data = eval_bandwidth,
mapping = aes(x = bw, y = cv_scores)) +
geom_point(data = eval_bandwidth,
mapping = aes(x = bw, y = cv_scores), color = 'red') +
geom_path(data = eval_bandwidth_adapt,
mapping = aes(x = bw, y = cv_scores)) +
geom_point(data = eval_bandwidth_adapt,
mapping = aes(x = bw, y = cv_scores), color = 'blue')+
labs(x = "Valeur de la bandwidth", y = "Valeur du CV")
```
La @fig-bwScoresNKDEadpt indique que les scores obtenus par les *bandwidths* adaptatives sont systématiquement supérieurs à ceux obtenus par les *bandwidths* fixes. Nous gardons une *bandwidth* pilote de 900 mètres pour recalculer notre ESC-NKDE avec une *bandwidth* adaptative.
```{r}
#| eval: false
intensity_adpt <- nkde.mc(lines = routes,
events = collisions,
w = rep(1, nrow(collisions)),
samples = lixels_centers,
kernel_name = 'quartic',
bw = 900,
adaptive = TRUE,
trim_bw = 1800,
method = 'continuous',
max_depth = 8,
digits = 1,
tol = 0.1,
agg = 5,
verbose = TRUE,
grid_shape = c(5,5))
```
```{r}
#| echo: true
#| message: false
#| warning: false
#| label: fig-mapNKDEadpt
#| fig-align: center
#| fig-cap: Densité des accidents sur le réseau routier de Sherbrooke avec une *bandwidth* adaptative
#| out-width: 100%
lixels$density_adpt <- intensity_adpt$k * 1000
tm_shape(lixels) +
tm_lines("density_adpt", lwd = 1.5, n = 7, style = "fisher",
legend.format = list(text.separator = "à"))+
tm_layout(frame=FALSE)
```
Comparativement aux résultats obtenus avec les *bandwidths* fixes, nous constatons que le lissage est beaucoup plus faible et que les points chauds sont plus faciles à identifier. Le *package* `spNetwork` permet aussi d'utiliser la méthode des *k* plus proches voisins comme *bandwidth* locale.
```{r}
#| echo: true
#| message: false
#| warning: false
knn_net <- spNetwork::network_knn(collisions,
lines = routes,
k = 30,
maxdistance = 3000,
grid_shape = c(1,1),
verbose = FALSE)
dist_mat <- knn_net$distances
# Nous limitons les bandwidths avec des bornes de 100 à 3000 m
dist_mat <- ifelse(dist_mat > 3000, 3000, dist_mat)
dist_mat <- ifelse(dist_mat < 100, 100, dist_mat)
```
```{r}
#| eval: false
eval_bandwidth_knearest <- bw_cv_likelihood_calc(
bws = NULL,
mat_bws = dist_mat,
lines = routes,
events = collisions,
w = rep(1, nrow(collisions)), # le poids de chaque évènement sera 1
kernel_name = 'quartic',
method = 'discontinuous',
adaptive = TRUE,
trim_bws = NULL,
max_depth = 10,
digits = 1,
tol = 0.1,
agg = 5, # tous les accidents dans un rayon de 5 mètres seront agrégés
grid_shape = c(5,5),
verbose = TRUE
)
```
```{r}
#| echo: true
#| message: false
#| warning: false
#| label: fig-bwSscoresNKDEK
#| fig-align: center
#| fig-cap: Scores des bandwidths adaptatives par *k* plus proches voisins
#| out-width: 65%
eval_bandwidth_knearest$knn <- 1:30
ggplot() +
geom_path(data = eval_bandwidth_knearest,
mapping = aes(x = knn, y = cv_scores)) +
geom_point(data = eval_bandwidth_knearest,
mapping = aes(x = knn, y = cv_scores), color = 'red')+
labs(x = "Nombre de plus proches voisins (k)", y = "Valeur du CV")
```
La @fig-bwSscoresNKDEK indique que le meilleur score est obtenu pour une *bandwidth* allant jusqu'au 16^e^ voisin.
```{r}
#| eval: false
intensity_adpt_knn <- nkde.mc(lines = routes,
events = collisions,
w = rep(1, nrow(collisions)),
samples = lixels_centers,
kernel_name = 'quartic',
bw = dist_mat[,16],
trim_bw = 1800,
method = 'continuous',
max_depth = 8,
digits = 1,
tol = 0.1,
agg = 5,
verbose = TRUE,
grid_shape = c(5,5))
```
```{r}
#| echo: true
#| message: false
#| warning: false
#| label: fig-mapNKDEknn
#| fig-align: center
#| fig-cap: Densité des accidents sur le réseau routier de Sherbrooke avec une *bandwidth* adaptative pour 16 plus proches voisins
#| out-width: 100%
lixels$density_adpt_knn <- intensity_adpt_knn * 1000
tm_shape(lixels) +
tm_lines("density_adpt_knn", lwd = 1.5, n = 7, style = "fisher",
legend.format = list(text.separator = "à"))+
tm_layout(frame=FALSE)
```
### Estimation de la densité spatio-temporelle sur un réseau {#sec-0623}
Comme nous avons pu le voir dans la [section @sec-0343], il est courant d'analyser des données d'évènements disposant à la fois d'une localisation dans l'espace et dans le temps. Il est alors possible de calculer une densité spatio-temporelle, soit de lisser les évènements à la fois dans la dimension spatiale et dans la dimension temporelle. Plus exactement, la densité d'un évènement *i* en un point *p* et un instant *t* correspond au produit de la densité spatiale et de la densité temporelle de *i*.
Cette extension est aussi valide dans le cas de l'analyse d'évènement sur réseau.
$$
\hat{\lambda}_{h_n h_t}\left(u_{n t}\right)=\frac{1}{h_n h_t} \sum_{i=1}^N k_{n e t}\left(\frac{\operatorname{dist}_{n e t}\left(u_{n t}, e_i\right)}{h_n}\right) \sum_{i=1}^N k_{\text {time }}\left(\frac{\operatorname{dist}_{\text {time }}\left(u_{n t}, e_i\right)}{h_t}\right)
\text{ avec :}
$$
- $h_t$ et $h_n$ les *bandwidths* temporelle et réseau.
- $u_{nt}$ un évènement localisé au point *n* du réseau et à l'instant *t* dans le temps.
La @fig-TNKDEvis illustre le calcul de la TNKDE, soit l'estimation de la densité spatio-temporelle sur un réseau (*Temporal Network Kernel Density Estimate*).
![La TNKDE comme produit des densités spatiale et temporelle](images/Chap06/TNKDE_produit.jpg){#fig-TNKDEvis width="85%" fig-align="center"}
Comme pour le NKDE, il est possible de :
- Utiliser des *bandwidths* variant localement dans l'espace et le temps.
- Comparer des *bandwidths* par des méthodes de validation croisée.
- Appliquer des correctifs aux frontières spatio-temporelles de la zone d'étude.
#### Application dans R {#sec-06231}
Nous reprenons simplement l'exemple de la section sur la NKDE et voir comment l'étendre au contexte spatio-temporel. Pour cela, nous utiliserons principalement le *package* `spNetwork`.
```{r}
#| echo: true
#| message: false
#| warning: false
library(sf)
library(spNetwork)
library(lubridate)
library(metR)
library(future) # pour accélérer les calculs de spNetwork
future::plan(future::multisession(workers = 5))
routes <- st_read('data/chap01/shp/Segments_de_rue.shp', quiet = TRUE)
collisions <- st_read('data/chap04/DataAccidentsSherb.shp', quiet = TRUE)
## Préparation de la colonne avec les dates
collisions$dt <- as_date(collisions$DATEINCIDE)
collisions$dt_num <- as.numeric(collisions$dt - min(collisions$dt))
## Reprojection dans le même système
routes <- st_transform(routes, 32187)
collisions <- st_transform(collisions, 32187)
routes <- sf::st_cast(routes, 'LINESTRING')
routes$length <- st_length(routes)
## Préparation des routes et des lixels
routes <- sf::st_cast(routes, 'LINESTRING')
```
```{r}
#| echo: false
#| eval: true
#| message: false
#| warning: false
load('data/chap06/pre_calculated_results_tnkdeA.rda')
load('data/chap06/pre_calculated_results_tnkdeB.rda')
```
Nous commençons par compléter le réseau routier. En effet, certaines sections sont isolées et forment des enclaves inaccessibles. Nous avons ignoré cette problématique jusqu'ici, mais nous verrons comment retirer les petites enclaves déconnectées de la partie principale du réseau. Pour cela, nous commençons par créer un objet de type `graph` à partir des routes avec le *package* `spNetwork`.
```{r}
#| echo: true
#| message: false
#| warning: false
#| eval: true
#| label: fig-networkComponents
#| fig-align: center
#| fig-cap: Visualisation des composantes du réseau routier
#| out-width: 85%
library(igraph)
library(dbscan)
library(tmap)
routes <- sf::st_cast(routes, 'LINESTRING')
routes$length <- st_length(routes)
graph <- spNetwork::build_graph(routes, digits = 2,line_weight = "length")
parts <- components(graph$graph)
graph$spvertices$part <- as.character(parts$membership)
tm_shape(graph$spvertices) +
tm_dots("part", size = 0.1)
```
À la @fig-networkComponents, nous pouvons identifier la partie principale du réseau et les segments déconnectés (éléments avec des valeurs supérieures à 1). Puis, nous soustrayons ces éléments du réseau pour alléger le graphe et éviter d'associer des collisions avec des parties inaccessibles du réseau routier.
```{r}
#| echo: true
#| message: false
#| warning: false
#| eval: true
#| label: fig-networkComponents2
#| fig-align: center
#| fig-cap: Visualisation des composantes du réseau routier
#| out-width: 85%
main_component <- subset(graph$spvertices, graph$spvertices$part == "1")
main_network <- subset(graph$spedges,
(graph$spedges$start_oid %in% main_component$id) |
(graph$spedges$end_oid %in% main_component$id)
)
main_network <- subset(main_network, as.numeric(st_length(main_network)) > 0)
```
Maintenant que nous avons nettoyé notre réseau, nous calculons les scores pour les *bandwidths*.
```{r}
#| echo: true
#| eval: false
#| message: false
#| warning: false
lixels_main <- lixelize_lines(main_network, 100, mindist = 50)
lixels_main_centers <- spNetwork::lines_center(lixels_main)
# Calcul des scores pour les bandwidths
cv_scores_tnkde <- bw_tnkde_cv_likelihood_calc(
bws_net = seq(700, 1500, 100),
bws_time = seq(10, 40, 5),
lines = main_network,
events = collisions,
time_field = "dt_num",
w = rep(1, nrow(collisions)),
kernel_name = "quartic",
method = "continuous",
max_depth = 10,
digits = 2,
tol = 0.1,
agg = 10,
grid_shape = c(5,5),
verbose = TRUE)
```
```{r}
#| echo: true
#| message: false
#| warning: false
#| label: fig-scorestnkde
#| fig-align: center
#| fig-cap: Scores obtenus pour différentes combinaisons de *bandwidths* spatiales et temporelles
#| out-width: 85%
# Création d'un graphique pour visualiser les résultats
library(ggplot2)
df2 <- reshape2::melt(cv_scores_tnkde)
ggplot(df2) +
geom_tile(aes(x = Var1, y = Var2, fill = value)) +
geom_contour(aes(x = Var1, y = Var2, z = value),
breaks = c(-400,-300, -250, -200, -180, -150),
color = 'white', linetype = 'dashed')+
scale_fill_viridis_c() +
labs(x = "Bandwidth spatiale (mètres)",
y = "Bandwidth temporelle (jours)",
fill = "cv score") +
coord_fixed(ratio=30)
```
La @fig-scorestnkde indique clairement que des *bandwidths* plus larges produisent de meilleurs résultats. Pour éviter d'avoir des résultats trop lissées, nous choisisons dans un premier temps la paire de *bandwidths* 30 jours et 1500 m. Puisque le temps de calcul peut être assez long compte tenu de la longueur des *bandwidths* (supérieure à 1 km), nous continuons donc à utiliser une NKDE discontinue.
```{r}
#| echo: true
#| eval: false
#| message: false
#| warning: false