Existen muchas funciones distintas para leer datos dependiendo del formato en el que están guardados. Para datos tabulares, la forma más útil es el formato csv, que es un archivo de texto plano con datos separados por coma. Vamos a trabajar con datos de temperatura de una estación meteorológica en Bariloche:
library(readr)
<- read_csv("datos/bariloche.csv") bariloche
## Rows: 3024 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): Temperatura
## dttm (1): Fecha
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Todo ese texto naranja/rojo es intimidante pero no te preocupes, es
sólo un mensaje que nos informa que los datos se leyeron y qué tipo de
dato tiene cada columna. ¿Notás algo raro? A pesar de que claramente la
temperatura es un número, la columna Temperatura
se está
leyendo como un caracter. Otra forma de ver esto es usando la a función
str()
(de structure en inglés):
str(bariloche)
## spec_tbl_df [3,024 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ Fecha : POSIXct[1:3024], format: "2011-05-28" "2011-05-29" ...
## $ Temperatura: chr [1:3024] NA "4.272223" "-999.99" "3.473611" ...
## - attr(*, "spec")=
## .. cols(
## .. Fecha = col_datetime(format = ""),
## .. Temperatura = col_character()
## .. )
## - attr(*, "problems")=<externalptr>
Donde se ve que la columna Temperatura
tiene clase
chr
(caracter).
Seguramente lo primero que tratarás en un caso así es simplemente cambiarle el tipo a la columna
library(dplyr)
%>%
bariloche mutate(Temperatura = as.numeric(Temperatura))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion
## # A tibble: 3,024 × 2
## Fecha Temperatura
## <dttm> <dbl>
## 1 2011-05-28 00:00:00 NA
## 2 2011-05-29 00:00:00 4.27
## 3 2019-06-15 00:00:00 -1000.
## 4 2011-05-31 00:00:00 3.47
## 5 2011-06-01 00:00:00 2.90
## 6 2011-06-02 00:00:00 6.26
## 7 2011-06-03 00:00:00 3.45
## 8 2016-03-22 00:00:00 -1000.
## 9 2011-06-05 00:00:00 5.00
## 10 2011-06-06 00:00:00 5.45
## # … with 3,014 more rows
Pero R dice que hay un problema.
NAs introduced by coercion
significa que algunos valores no
se pudieron transformar en numéricos y entonces as.numeric
los dejó como valores faltantes. Casi con seguridad no querés eso. Para
ver qué está pasando, hay que investigar qué contenidos tienen esas
filas. Para eso, podés filtrar las filas de bariloche
que
no tienen NA
en Temperatura
pero que se
convierten en NA
al hacer
as.numeric(Temperatura)
:
%>%
bariloche filter(!is.na(Temperatura) & is.na(as.numeric(Temperatura)))
## Warning in mask$eval_all_filter(dots, env_filter): NAs introduced by coercion
## # A tibble: 302 × 2
## Fecha Temperatura
## <dttm> <chr>
## 1 2018-12-16 00:00:00 N/A
## 2 2011-06-02 00:00:00 N/A
## 3 2019-07-19 00:00:00 N/A
## 4 2020-05-13 00:00:00 N/A
## 5 2015-12-08 00:00:00 N/A
## 6 2015-05-23 00:00:00 N/A
## 7 2015-04-27 00:00:00 N/A
## 8 2014-09-10 00:00:00 N/A
## 9 2019-12-23 00:00:00 N/A
## 10 2019-07-10 00:00:00 N/A
## # … with 292 more rows
Ajá! Se ve que a alguien se le ocurrió usar los caracteres “N/A” para
designar algunos datos faltantes. Con esta información, estaría bueno
volver a leer los datos, diciéndole a read_csv
que este
archivo usa dos formas para referirse a datos faltantes.
Desafío andá a la ayuda de read_csv
y
fijate si hay algún argumento que controle la lectura de datos
faltantes. Sabiendo que hay valores faltantes codificados como “N/A”,
¿qué deberías pasarle a ese argumento para leer correctamente los
datos?
read_csv
tiene un argumento na
que controla
qué valores van a interpretarse como valores faltantes. En este caso,
sabés que “N/A” es uno. Entonces debería funcionar buen leer los datos
así:
<- read_csv("datos/bariloche.csv", na = "N/A") bariloche
## Rows: 3024 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): Temperatura
## dttm (1): Fecha
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
¡No! Otra vez Temperatura
se leyó como caracter. Si usás
str
podés ver qué pasó:
str(bariloche)
## spec_tbl_df [3,024 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ Fecha : POSIXct[1:3024], format: "2011-05-28" "2011-05-29" ...
## $ Temperatura: chr [1:3024] "NA" "4.272223" "-999.99" "3.473611" ...
## - attr(*, "spec")=
## .. cols(
## .. Fecha = col_datetime(format = ""),
## .. Temperatura = col_character()
## .. )
## - attr(*, "problems")=<externalptr>
La primera fila, que antes se leía como dato faltante, ahora se lee como “NA”.
Desafío ¿Por qué ahora read_csv
está
leyendo ese dato incorrectamente? Consejo: volvé a la ayuda de
read_csv
y fijate el valor por default del argumento
na
.
Antes, na
estaba como el valor por defecto, que es
c("", "NA")
, lo que significa que tanto valores vacíos como
valores "NA"
son considerados valores faltantes. Lo que
tenés que hacer es agregar también el valor "N/A"
:
<- read_csv("datos/bariloche.csv", na = c("", "NA", "N/A")) bariloche
## Rows: 3024 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (1): Temperatura
## dttm (1): Fecha
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Bien. Ahí la columna Temperatura
se leyó como doble.
Pero, ¿se leyeron bien? Una forma rápida de chequear los datos es usando
la función summary
:
summary(bariloche)
## Fecha Temperatura
## Min. :2011-05-28 00:00:00.00 Min. : -999.990
## 1st Qu.:2013-06-06 00:00:00.00 1st Qu.: 4.617
## Median :2016-04-30 12:00:00.00 Median : 8.620
## Mean :2016-02-25 15:23:48.56 Mean : -98.634
## 3rd Qu.:2018-07-04 06:00:00.00 3rd Qu.: 13.243
## Max. :2020-09-12 00:00:00.00 Max. : 25.710
## NA's :501
summary
devuelve algunas estadísticas simples sobre cada
columna. La columna Temperatura
tiene 501 valores
faltantes, su máximo es 25.70972 y su mínimo es… ¿-999.990? Eso no puede
ser. Seguramente a otro alguien se le ocurrió usar “-999.99” para marcar
los datos faltantes. Una vez que sabés eso, podés volver a leer otra vez
los datos agregando otro marcador más para los datos faltantes.
<- read_csv("datos/bariloche.csv", na = c("", "NA", "N/A", "-999.99")) bariloche
## Rows: 3024 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (1): Temperatura
## dttm (1): Fecha
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Y ahora summary
da:
summary(bariloche)
## Fecha Temperatura
## Min. :2011-05-28 00:00:00.00 Min. :-4.806
## 1st Qu.:2013-06-06 00:00:00.00 1st Qu.: 5.994
## Median :2016-04-30 12:00:00.00 Median : 9.531
## Mean :2016-02-25 15:23:48.56 Mean : 9.832
## 3rd Qu.:2018-07-04 06:00:00.00 3rd Qu.:13.681
## Max. :2020-09-12 00:00:00.00 Max. :25.710
## NA's :772
Ahora el rango de temperatura es razonable y podés tener confianza de que los datos están bien leídos.
Junto a read_csv()
hay una familia de funciones
similares que leen archivos con distintos tipos de delimitadores
distintos de la coma. La buena noticia es que todas las funciones tiene
argumentos similares y se usan casi indistintamente.
Si quisieras leer archivos que están en formato “xlsx” (Excel) podés
usar el paquete {readxl}
.
Luego de esta epopeya para conseguir leer estos datos correctamente está bueno reflexionar sobre la importancia de los metadatos; los datos sobre los datos que, entre otras cosas, podrían indicar cómo están codificados los datos faltantes. Una alternativa es usar formatos de datos “autodescriptivos”. Estos son tipos de archivos que contienen sus propios metadatos.
Uno de estos tipos de formatos muy utilizado en ciencias para guardar datos atmosféricos grillados es el NetCDF. Existen varios paquetes para estos archivos. Uno de ellos es {metR}.
library(metR)
Los datos en archivos NetCDF puede ser bastante grandes y destruir tu
sesión de R si tratás de leerlos enteros en memoria. Por eso, siempre es
bueno primero fijarse qué tiene un archivo nuevo. Para eso, {metR} tiene
la función GlanceNetCDF
que muestra un vistazo de los
metadatos del archivo. Vamos a trabajar con un archivo en este formato
que contiene datos de reanálisis del NCEP.
GlanceNetCDF("datos/temperatura.nc")
## ----- Variables -----
## air:
## mean Daily Air temperature in degK
## Dimensions: lon by lat by level by time
##
##
## ----- Dimensions -----
## time: 1 values from 2010-07-09 to 2010-07-09
## level: 17 values from 10 to 1000 millibar
## lat: 73 values from -90 to 90 degrees_north
## lon: 144 values from 0 to 357.5 degrees_east
La salida de esta función muestra las variables que tiene el archivo y sus dimensiones.
Desafío ¿Qué información podés deducir sobre el contenido de temperatura.nc a partir de esto?
En este caso, el archivo tiene una sola variable, llamada
air
que es la temperatura media en Kelvin (un archivo
NetCDF puede tener muchas variables y no todas en la misma grilla). De
las dimensiones del archivo, se ve que tiene dimensiones de tiempo,
nivel, longitud y latitud. La latitud va de -90 a 90 y la longitud de 0
a 357.5, por lo que es un campo global. level
es la
coordenada vertical, que va de 1000 milibares (básicamente la
superficie) a 10 millibares (la estratósfera media).
Para leer los datos, se usa ReadNetCDF()
:
<- ReadNetCDF("datos/temperatura.nc", vars = "air")
temperatura temperatura
## time level lat lon air
## 1: 2010-07-09 1000 90 0.0 274.87
## 2: 2010-07-09 1000 90 2.5 274.87
## 3: 2010-07-09 1000 90 5.0 274.87
## 4: 2010-07-09 1000 90 7.5 274.87
## 5: 2010-07-09 1000 90 10.0 274.87
## ---
## 178700: 2010-07-09 10 -90 347.5 188.25
## 178701: 2010-07-09 10 -90 350.0 188.25
## 178702: 2010-07-09 10 -90 352.5 188.25
## 178703: 2010-07-09 10 -90 355.0 188.25
## 178704: 2010-07-09 10 -90 357.5 188.25
Si no le ponés vars = "air"
, ReadNetCDF()
va a tratar de leer todas las variables presentes en el archivo. Esto a
veces puede causar problemas si las grillas de las variables no son
compatibles (por ejemplo, si una variable es la temperatura del aire
definida en distintas alturas, y otra es la temperatura del suelo). En
este caso, como “temperatura.nc” tiene sólo una variable, no haría falta
ponerlo.
Es muy posible que quieras datos sólo de una región. Una opción es leer todo el archivo y luego filtrar los datos. Si te interesa la temperatura de superficie de Argentina, podrías hacer algo así:
%>%
temperatura filter(level == 1000 & between(lat, -65, -20) & between(lon, 280, 310))
## time level lat lon air
## 1: 2010-07-09 1000 -20 280.0 287.62
## 2: 2010-07-09 1000 -20 282.5 287.70
## 3: 2010-07-09 1000 -20 285.0 289.72
## 4: 2010-07-09 1000 -20 287.5 293.25
## 5: 2010-07-09 1000 -20 290.0 296.32
## ---
## 243: 2010-07-09 1000 -65 300.0 267.92
## 244: 2010-07-09 1000 -65 302.5 268.15
## 245: 2010-07-09 1000 -65 305.0 268.67
## 246: 2010-07-09 1000 -65 307.5 269.47
## 247: 2010-07-09 1000 -65 310.0 270.35
Pero es mucho más eficiente leer sólo los datos que te interesan,
especialmente en archivos NetCDF grandes, que incluso no puede ser
leídos en memoria enteros. ReadNetCDF()
tiene un argumento
llamado subset
que sirve para especificar qué datos
leer:
ReadNetCDF("datos/temperatura.nc", vars = "air",
subset = list(level = 1000,
lat = c(-65, -20),
lon = c(280, 310)))
## time level lat lon air
## 1: 2010-07-09 1000 -20 280.0 287.62
## 2: 2010-07-09 1000 -20 282.5 287.70
## 3: 2010-07-09 1000 -20 285.0 289.72
## 4: 2010-07-09 1000 -20 287.5 293.25
## 5: 2010-07-09 1000 -20 290.0 296.32
## ---
## 243: 2010-07-09 1000 -65 300.0 267.92
## 244: 2010-07-09 1000 -65 302.5 268.15
## 245: 2010-07-09 1000 -65 305.0 268.67
## 246: 2010-07-09 1000 -65 307.5 269.47
## 247: 2010-07-09 1000 -65 310.0 270.35
A subset
hay que pasarle una lista donde cada elemento
tiene el nombre de una dimensión con un vector cuyo rango define el
rango de los datos a leer. El código de arriba, entonces, dice que de la
dimensión level
lea únicamente el valor 1000, de la
dimensión lat
, lea los valores que van entre -65 y -20, y
de la dimensión lon
, los valores entre 280 y 310.
Desafío Elegí una región del mundo y completá el código que sigue para que lea la temperatura en superficie de esa región.
<- ReadNetCDF("datos/temperatura.nc", vars = "air",
temperatura subset = list(level = 1000,
lat = c(___, ___),
lon = c(___, ___)))
Para revisar si leíste los datos correctos, corré este código (no te preocupes si no usas ggplot, en una sección más adelante lo veremos)
library(ggplot2)
ggplot(temperatura, aes(lon, lat)) +
geom_raster(aes(fill = air)) +
geom_path(data = map_data("world2"), aes(long, lat, group = group))
Ponerle nombre a las variables es a veces la parte más difícil de
escribir código. A R no le viene bien cualquier nombre de variable
siempre y cuando no empiece con un número o un “_“. Pero a los seres
humanos que lean el código y tengan que interpretarlos les va a resultas
más fácil entender qué hace la variable temperatura_cordoba
que la variable xxy1
.
El consejo es tratar en lo posible usar nombre descriptivos y consistentes. Por ejemplo, siempre usar minúsculas y separar palabras con “_“.
Tip: Para hacerse la vida más fácil existen “guías de estilo” para programar que explicitan reglas específicas para escribir código. Por ejemplo esta o esta otra. Se trata de reglas únicamente para los ojos humanos, y que no afectan en absoluto la eficiencia o correctitud de la programación. En general, no existen guías buenas o malas, la idea es elegir una y ser consistente. De esta manera, vas a poder entender tu código con más facilidad.