-
Notifications
You must be signed in to change notification settings - Fork 3
/
03_graficado.Rmd
291 lines (213 loc) · 11.2 KB
/
03_graficado.Rmd
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
---
title: "Graficando"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(
echo = TRUE,
fig.height = 3,
fig.width = 5,
message = FALSE,
warning = FALSE
)
```
Ahora que tenés los datos en forma de tabla, todo lo que queda es usar las mismas herramientas que usarías para trabajar con cualquier otro tipo de dato.
```{r}
# Cargo los paquetes necesarios
library(magrittr)
library(ggplot2)
library(dplyr)
library(data.table)
library(metR)
# Leo los datos
#
sst <- ReadNetCDF("datos/temperatura_mar.nc", vars = "sst")
# Me quedo con un solo campo, para graficar
sst1 <- sst[time == time[1]]
sst1 <- sst %>%
filter(time == time[1])
```
Ya vimos una forma rápida de graficar este tipo de datos usando `geom_raster()`:
```{r}
sst1 %>%
ggplot(aes(longitude, latitude)) +
geom_raster(aes(fill = sst))
```
Con `geom_raster()`, cada punto de grilla es representado como un rectángulo cuyo color de relleno se mapea al valor de la variable, por lo tanto la estética a usar es `fill`.
## Contornos
Otra forma de mostrar este tipo de campos (o sea, que pinta tiene la variable en una región) es usando contornos con `geom_contour()`.
A mí me gusta usar `metR::geom_contour2()` porque usa contornos negros por default y tiene otras funcionalidades útiles. En este caso, cada línea corresponde a un valor específico de la variable y por eso usamos el argumento `z` para mapearla.
```{r}
sst1 %>%
ggplot(aes(longitude, latitude)) +
geom_contour2(aes(z = sst))
```
El problema es que ahora perdemos los continentes gratis y no tenemos información sobre que valores representa cada línea.
Podemos obtener un resultado intermedio usando contornos llenos con `metR::geom_contour_fill()` (de nuevo, ggplot2 tiene un `geom_contour_filled()` pero yo prefiero los defaults y otras funcionalidades de `geom_contour_fill()`).
```{r}
sst1 %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst))
```
En este punto estaría bueno tener no tener los continentes pixelados (esto se debe a la baja resolución de los datos principalmente).
El paquete **rnaturalearth** tiene datos de costas, países y regiones en distintas resoluciones. Pero por ahora podemos evitar instalar otro paquete, usando la función `map_data()` de ggplot2.
```{r}
ggplot() +
geom_polygon(data = map_data("world2"), aes(long, lat, group = group),
fill = "white")
```
Ahora podemos poner eso encima del gráfico anterior.
```{r}
mapa <- geom_polygon(data = map_data("world2"), aes(long, lat, group = group),
fill = "white", inherit.aes = FALSE)
```
```{r}
sst1 %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst)) +
mapa
```
Aún con esto seguimos viendo las cosas "pixeladas".
Una forma de resolver esto es rellenando las regiones sin datos con un valor razonable.
`metR::geom_contour_fill()` tiene el argumento `na.fill` que si es `TRUE`, interpola los datos faltantes:
```{r}
sst1 %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst), na.fill = TRUE)
```
Sabemos que los datos bien adentro de los continentes no tienen sentido, pero si lo que vamos a hacer es taparlos con el mapa, no tiene mucha importancia.
```{r}
sst1 %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst), na.fill = TRUE) +
mapa
```
## Usando proyecciones
Hasta ahora usamos datos grillados en una grilla regular en longitud y latitud, pero no todos los datos son así.
Por ejemplo el dataset de ejemplo `surface` tiene datos de topografía del centro de Argentina.
```{r}
head(surface)
```
Vemos que tiene datos de topografía en longitud y latitud, en este espacio los datos no son regulares ya que tiene en cuenta la curvatura de la Tierra. Pero además en el data.frame hay dos columnas x e y que corresponde a la distancia en metros entre las observaciones o datos al punto central de la proyección de estos datos. Vamos a ver qué pinta tienen cuando graficamos en función de la latitud y la longitud:
```{r}
surface %>%
ggplot(aes(lon, lat)) +
geom_point(aes(colour = height))
```
Claramente esto no es un cuadrado, pero hay una cierta regularidad en la ubicación de los puntos.
La grilla sí es regular, pero en la proyección de Lambert. Si graficamos usando la información en x e y (o sea los datos "desproyectados") nos encontramos con lo siguiente:
```{r}
surface %>%
ggplot(aes(x, y)) +
geom_raster(aes(fill = height))
```
En esta grilla regular si podemos graficar contornos sin problema.
```{r}
surface %>%
ggplot(aes(x, y)) +
geom_contour_fill(aes(z = height))
```
Una forma de definir esa proyección es con una "proj-string"; un texto que define de qué proyección se trata y cuáles son sus parámetros.
(Aunque notar que este paradigma [ya se quedó viejo](https://inbo.github.io/tutorials/tutorials/spatial_crs_coding/))
¿De donde sale esta string?
¡De los metadatos!
Si tu archivo netCDF está bien hecho, parte de sus metadatos va a ser la proyección.
En este caso, el "metadato" es este documento y la proj-string es la siguiente:
```{r}
proj_string <- paste0("+proj=lcc +lat_1=-30.9659996032715 +lat_2=-30.9659996032715 +lat_0=-30.9660034179688 +lon_0=296.432998657227 +a=6370000 +b=6370000 +over")
```
Si incluimos esta información en argumento `proj` de `geom_contour_fill()` podemos pasar de las coordenadas proyectadas x/y a lon/lat y visualizar la región de manera correcta.
```{r}
surface %>%
ggplot(aes(x, y)) +
geom_contour_fill(aes(z = height), proj = proj_string)
```
## Graficando más de una variable
Muchas veces (al menos en meteorología!) queremos comparar varias variables al mismo tiempo y haces 2 gráficos no suele ser suficiente, queremos superponer las variables. ggplot2 tiene algunas limitaciones no podemos mapear el color a 2 variables distintas, al menos no directamente.
Una solución sería usar contornos vacíos y etiquetas para identificar valores (o aplicando color) sobre contornos llenos que usa el relleno o fill para definir los valores. De esa manera tendremos leyendas distintas (color -o etiquetas- y fill) para analizar las 2 variables.
Otra forma de plotear varias escalas de colores en un mismo ggplot es usando el paquete [ggnewscale](https://eliocamp.github.io/ggnewscale/). En este ejemplo estamos graficando la topografía de la superficie de los continentes y del fondo del mar. Si bien podríamos trabajar con los datos como una única variable, suele ser útil diferenciarla.
Primero graficamos los datos donde h (la altura) es positiva (los continentes) y luego agregamos `ggnewscale::new_scale_fill()` para *habilitar* una nueva escala de relleno y usarla para los datos con valor negativo (el fondo del océano).
```{r}
topo <- GetTopography(270, 320, -20, -60, resolution = 1/30)
topo %>%
ggplot(aes(lon, lat)) +
geom_contour_fill(aes(z = h), data = ~.x[h > 0]) +
scale_fill_gradientn(colours = terrain.colors(4)) +
ggnewscale::new_scale_fill() +
geom_contour_fill(aes(z = h), data = ~.x[h <= 0])
```
## Guía discretizada
Un último truco que hace ayuda a interpretar los contornos llenos es cambiar la guía de colores.
`geom_contour_fill()` por defecto usa una escala continua para el aes fill, por lo que la guía de colores es un degradé continuo.
El problema de eso es que no respeta necesariamente la idea de que `geom_contour_fill()` en cierta forma está discretizando los datos.
Una primera solución es usar una variable discreta.
Para esto hay que cambiar el aes fill a `..level..`, que es la variable "level" computada por el geom.
Esta es una versión discreta de la variable usada por default (`..level_mid..`), lo cual fuerza a gplot2 a elegir una escala discreta junto con su guía.
```{r}
sst1 %>%
.[longitude %between% c(270, 320) & latitude %between% c(-60, -20)] %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst, fill = ..level..), na.fill = TRUE) +
mapa +
coord_quickmap(xlim = c(270, 320), ylim = c(-60, -20))
```
El problema con esta escala es que no reconoce que si bien los datos ahora son discretos, ¡éstos representan números continuos!
Para ilustrar el problema, veamos qué pasa si usamos breaks que no son equidistantes.
```{r}
sst1 %>%
.[longitude %between% c(270, 320) & latitude %between% c(-60, -20)] %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst, fill = ..level..), na.fill = TRUE,
breaks= c(272, 275, 280, 295, 300)) +
mapa +
coord_quickmap(xlim = c(270, 320), ylim = c(-60, -20))
```
Como la escala es discreta, no tiene forma de interpretar que el "ancho" de la región entre 272 y 275 es muy distinto que el ancho entre 280 y 295.
Esto se puede remediar usando una escala discretizada.
Cualquier escala contínua se puede convertir en una escala discretizada agregando `super = metR::ScaleDiscretised` (el `metR::` no es necesario si tenés metR cargado, pero está acá para indicar en de qué paquete sale).
Además, hay que usar la escala colorsteps de ggplot2 con el argumento `even.steps = FALSE`.
```{r}
sst1 %>%
.[longitude %between% c(270, 320) & latitude %between% c(-60, -20)] %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst, fill = ..level..), na.fill = TRUE,
breaks= c(272, 275, 280, 295, 300)) +
scale_fill_viridis_c(option = "B", super = metR::ScaleDiscretised,
guide = guide_colorsteps(even.steps = FALSE)) +
mapa +
coord_quickmap(xlim = c(270, 320), ylim = c(-60, -20))
```
## Contornos iluminados
Muchas veces los datos tienen un punto medio lógico y interesa ver las desviaciones con respecto a ese punto medio.
En ese caso, es muy útil usar una escala de colores divergente.
Para esto se puede usar `scale_fill_gradient2()` o `metR::scale_fill_divergent()` (que es lo mismo pero con el default razonable de que valores mayores al punto medio están en rojo y valores menores se pintan de azul), o la versión discretizada `metR::scale_fill_divergent_discretised()`.
```{r}
sst1 %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst, fill = ..level..), na.fill = TRUE) +
scale_fill_divergent_discretised(midpoint = 290) +
mapa
```
¿Pero qué pasa si este gráfico alguien lo veo imprime en blanco y negro?
Lo que pasa es que los valores mayores al punto medio se ven iguales que los valores menores al punto medio.
```{r}
colorblindr::edit_colors(last_plot(), colfun = colorspace::desaturate) %>%
cowplot::plot_grid()
```
Una posible solución es agregarle un efecto de relieve a los contornos usando `metR::geom_contour_tanaka()`.
```{r}
sst1 %>%
ggplot(aes(longitude, latitude)) +
geom_contour_fill(aes(z = sst, fill = ..level..), na.fill = TRUE) +
geom_contour_tanaka(aes(z = sst), na.fill = TRUE) +
scale_fill_divergent_discretised(midpoint = 290) +
mapa
```
Además de quedar bonitos, ahora es muy fácil saber si un detarminado valor es una anomalía positiva o negativa aún en blanco y negro.
```{r}
colorblindr::edit_colors(last_plot(), colfun = colorspace::desaturate) %>%
cowplot::plot_grid()
```
::: {.alert .alert-info}
El paquete **colorblindr** es muy útil para ver que pinta tiene el gráfico según distintos tipos de ceguera del color. Podés instalarlo con `remotes::install_github("clauswilke/colorblindr")`
:::