El año pasado dicté un curso de introducción al análisis de datos con R para estudiantes de Periodismo. Dos estudiantes del curso, V. y F., me preguntaron un día si era posible extraer los datos que estaban en esta entrada de Wikipedia sobre imágenes satelitales que no son accesibles al público. En ella hay una lista de países con zonas que aparecen borrosas o pixeladas en Google Maps. Para cada país se incluye una tabla con datos sobre esos lugares, como el nombre, la explicación y las coordenadas:

Cuando vi la página me pareció que se podría resolver de forma parecida a uno de los ejemplos de web scraping que habíamos visto en clases, así que decidí dedicar parte de la sesión a abordar este problema. Me pareció interesante mostrar en vivo y en directo cómo enfrentaría el desafío y las dificultades que van apareciendo. Y sobre todo, mostrar que fallar, estar mirando la documentación de las funciones y buscar en Google son parte central del proceso.

En este post reconstruiré el paso a paso para extraer las tablas que aparecen en esa entrada de Wikipedia y visualizar los datos que ahí aparecen. Hay muchas páginas (de Wikipedia y otras) en que la información que queremos aparece repartida en más de una tabla. Así que el procedimiento que seguiremos acá podría adaptarse a otros casos similares.

Algunas de las cosas que mostraremos:

  • cómo extraer información de más de una etiqueta html
  • cómo unir tablas en un solo dataframe
  • cómo lidiar con caracteres invisibles 😧
  • cómo usar expresiones regulares para limpiar datos
  • cómo hacer un mapa con el paquete {leaflet}

¿Podemos “escrapear” Wikipedia?

Hay ciertas preguntas que tenemos que hacernos antes de “escrapear” un sitio web. Obviamente, hay preguntas técnicas: ¿es viable extraer la información que está ahí? ¿Qué paquetes necesito para hacerlo? Pero también hay una pregunta ética: ¿puedo hacerlo? Para responder esta última pregunta tenemos que hacer dos cosas:

  1. Mirar los términos de uso del sitio web. En algunos casos se explicita que no está autorizada la utilización de herramientas de extracción de datos. Aquí, como ejemplo, está lo que dicen las condiciones de uso de Amazon, que prohiben ese tipo de técnicas:

  1. Si vemos que los términos de uso nos dicen que es posible extraer datos (o si en los términos de uso no dice explícitamente que no podemos hacerlo), tenemos que revisar luego el archivo robots.txt. Encontraremos ahí información importante sobre cómo podemos interactuar con el sitio web. El paquete {robotstxt} incluye algunas funciones que permiten revisar este archivo. En el caso de Wikipedia, nos pide que nos portemos bien y no vayamos muy rápido (por ejemplo, si estuviésemos extrayendo datos de varias páginas a la vez).
robotstxt::robotstxt("es.wikipedia.org")$comments %>% head()
##   line                                                               comment
## 1    1                # robots.txt for http://www.wikipedia.org/ and friends
## 2    2                                                                     #
## 3    3   # Please note: There are a lot of pages on this site, and there are
## 4    4 # some misbehaved spiders out there that go _way_ too fast. If you're
## 5    5              # irresponsible, your access to the site may be blocked.
## 6    6                                                                     #

En este caso solo queremos información de una página, así que no es necesario tomar precauciones sobre la velocidad de las peticiones. Pero si quisiéramos extraer información de varias páginas, tendríamos que tomar alguna precaución al respecto.

Antes de empezar: ¿cómo espero que se vean los datos al final del proceso? ✏️

Algo que siempre me ha resultado útil al procesar datos es imaginarme cómo esperaría que se viera el resultado final. En este caso, cómo imagino el data frame resultante luego de escrapear la página y limpiar y ordenar los datos. Lo que queremos es tener una fila por cada lugar no accesible y que el país sea una variable. Así que ese día en la clase dibujé algo así en la pizarra:

Tener esta imagen inicial me guía al decidir cómo extraer y transformar los datos. Por ejemplo, me permite darme cuenta que necesito escrapear tanto las tablas como los nombres de países, y que luego tengo que buscar una forma de unir todo en un solo data frame.

Identificar las etiquetas html con Selector Gadget 🔍

Lo primero, entonces, es saber qué exactamente es lo tengo que escrapear de esa página de Wikipedia. Selector Gadget es una extensión de Google Chrome que permite identificar las etiquetas html en un sitio web solo haciendo clic sobre el elemento que nos interesa. Al revisar, podemos ver que los nombres de los países están en h4:

Las tablas, como cabría esperar, están en table.

Ahora sí, vamos a R.

Los paquetes que utilizaremos 📦

install.packages("tidyverse") 
install.packages("janitor") 
install.packages("leaflet")
library(rvest) 
library(dplyr)
library(tidyr)
library(stringr)
library(janitor)
library(leaflet)

Importar los datos 📥

El primer paso es leer el código html de la página que nos interesa. Para ello, utilizaremos funciones del paquete {rvest}.

wikipedia_html <- read_html("https://es.wikipedia.org/wiki/Anexo:Im%C3%A1genes_satelitales_no_accesibles_al_p%C3%BAblico")

Tal como comentamos anteriormente, necesitamos ahora extraer los datos que están en dos etiquetas:

  • h4
  • table

Extraer los nombres de los países

Con la función html_nodes() indicamos que queremos todo lo que está en h4 y con html_text() especificamos que nos interesa solo el texto:

wikipedia_html %>%
  html_nodes("h4") %>%
  html_text()
##  [1] "Irak[editar]"                   "Pakistán[editar]"              
##  [3] "Corea del Norte[editar]"        "Corea del Sur[editar]"         
##  [5] "Japón[editar]"                  "Taiwán[editar]"                
##  [7] "Filipinas[editar]"              "Turkmenistán[editar]"          
##  [9] "Alemania[editar]"               "Dinamarca, Islas Faroe[editar]"
## [11] "Eslovaquia[editar]"             "España[editar]"                
## [13] "Francia[editar]"                "Irlanda[editar]"               
## [15] "Países Bajos[editar]"           "Polonia[editar]"               
## [17] "Portugal[editar]"               "Rusia[editar]"                 
## [19] "Suecia[editar]"                 "Estados Unidos[editar]"        
## [21] "Chile[editar]"

Funciona, pero sería mejor si elimináramos [editar]. Para ello, agregaremos una función más a nuestro bloque de código anterior: str_remove(). Esta función nos permite remover elementos de una cadena de caracteres. En este caso, tenemos que “escapar” los corchetes [ ] usando barras invertidas para que los interprete de forma literal, de lo contrario, creerá que estamos utilizando los corchetes para agrupar caracteres dentro de la expresión regular. Guardaremos esta cadena de caracteres en un objeto al que llamaremos paises.

paises <- wikipedia_html %>%
  html_nodes("h4") %>%
  html_text() %>%
  str_remove("\\[editar\\]")

paises
##  [1] "Irak"                   "Pakistán"               "Corea del Norte"       
##  [4] "Corea del Sur"          "Japón"                  "Taiwán"                
##  [7] "Filipinas"              "Turkmenistán"           "Alemania"              
## [10] "Dinamarca, Islas Faroe" "Eslovaquia"             "España"                
## [13] "Francia"                "Irlanda"                "Países Bajos"          
## [16] "Polonia"                "Portugal"               "Rusia"                 
## [19] "Suecia"                 "Estados Unidos"         "Chile"

Antes de seguir, tenemos que editar este objeto. Tenemos 21 nombres de países, pero hay solo 19 tablas. Corea del Norte y Corea del Sur aparecen mencionadas en la página, pero no se incluye una tabla con datos. Si no las eliminamos ahora, cuando tratemos de asociar cada nombre de país con su tabla nos aparecerá este error:

Error: Tibble columns must have compatible sizes. * Size 21: Existing data. * Size 19: Column at position 2. ℹ Only values of size one are recycled.

Exactamente eso pasó mientras hacía la clase 😬. Así que tuve que volver atrás, editar el objeto con los nombres de los países y re-ejecutar el código. Ahora que ya sé que eso ocurre, mejor resolvámoslo altiro. Como son pocos elementos, los podemos eliminar indicando su posición:

paises <- paises[-c(3:4)] 

Extraer las tablas

Para extraer las tablas haremos una operación similar, solo que cambiando la etiqueta y usando html_table() para indicar que queremos conservar las tablas con ese formato. Guardamos todo en un objeto al que llamaremos tablas:

tablas <- wikipedia_html %>%
  html_nodes("table") %>%
  html_table()

tablas es un objeto tipo lista que contiene 19 elementos: las 19 tablas que estaban en la página.

class(tablas)
## [1] "list"
length(tablas)
## [1] 19

Si miramos nuestro objeto con View(tablas), podemos hacernos una mejor idea de lo que hay en su interior.

Juntemos todo en un tibble

Ahora que tenemos todos los datos que necesitamos, los juntaremos en un data frame, específicamente, en un variante de data frame llamada tibble. ¿Por qué un tibble? Porque en una columna tendremos una fila por cada nombre de país y en la otra columna tendremos una tabla por fila. Es decir, crearemos una tabla que en su interior tiene otras tablas. La ventaja de crear un tibble es que se imprime de una manera que hace más fácil mirar los datos en la consola. Para ello, usaremos la función tibble() e indicaremos como argumentos los nombres que queremos que tengan las columnas y en qué objetos se encuentran esos datos actualmente.

no_accesibles <- tibble(pais = paises, lugares = tablas)

head(no_accesibles)
## # A tibble: 6 x 2
##   pais         lugares         
##   <chr>        <list>          
## 1 Irak         <df[,4] [1 × 4]>
## 2 Pakistán     <df[,4] [1 × 4]>
## 3 Japón        <df[,4] [1 × 4]>
## 4 Taiwán       <df[,4] [8 × 4]>
## 5 Filipinas    <df[,4] [2 × 4]>
## 6 Turkmenistán <df[,4] [1 × 4]>

Pero ¿qué es esto? no_accesibles es un data frame que tiene otros data frames en su interior. El formato tibble imprime la columna lugares de esa manera compacta para que sea más fácil trabajar con este objeto. En la clase hice un dibujo así en la pizarra para explicar qué es lo que teníamos en este momento:

La magia de unnest()

Si bien tenemos todos los datos en una sola tabla, los tenemos en dos niveles de profundidad, por decirlo de algún modo. En el dibujo que hicimos al inicio de cómo imaginábamos los datos, teníamos una fila por lugar no accesible, no una fila por país. Para subir de nivel (o “desanidar”) los data frames que tenemos en la columna lugares, usaremos la función unnest() (literalmente, desanidar) del paquete {tidyr}. Primero le indicamos el nombre de nuestro objeto y luego la columna a desanidar:

no_accesibles <- unnest(no_accesibles, lugares)

head(no_accesibles)
## # A tibble: 6 x 5
##   pais   Coordenadas              `Área/lugar`    Explicación             Enlace
##   <chr>  <chr>                    <chr>           <chr>                   <chr> 
## 1 Irak   30°34′2″N 47°46′6″E / …  Hotel Shatt al… Ya no está censurada. … [2]   
## 2 Pakis… 27°51′0″N 65°10′0″E / …  Base Aérea Pre… Los vehículos aéreos n… [3]   
## 3 Japón  24°17′21″N 153°58′41″E…  Aeropuerto de … El territorio más orie… [4]   
## 4 Taiwán 24°12′22″N 120°36′26″E…  Taichung        Antigua imagen de la b… [5]   
## 5 Taiwán 25°02′42″N 121°36′12″E…  Fábrica Milita… Superposición oval      [6]   
## 6 Taiwán 24°12′26″N 120°48′01″E…  Reservas milit… Dos reservas militares… [7]

¡Listo! Ya tenemos todos los datos en una sola tabla. Ahora, necesitamos un poco de orden.

Limpieza de datos

Algo que siempre hago cuando tengo que limpiar datos es partir con el nombre de las varibles. Si miramos cómo están actualmente, veremos que algunas tienen mayúscula inicial y que una de las variables tiene una barra, lo que nos obligaría a rodearla de tildes graves al incluirla en nuestro código (así: `Área/lugar`).

names(no_accesibles)
## [1] "pais"        "Coordenadas" "Área/lugar"  "Explicación" "Enlace"

Para resolver esto, podemos usar la función clean_names() del paquete {janitor}. Por defecto deja todos los nombres en snake case (o sea, minúsculas separadas por guion bajo), pero si uno prefiere otra formato (como camelCase), se puede indicar en los argumentos.

no_accesibles <- clean_names(no_accesibles)

names(no_accesibles)
## [1] "pais"        "coordenadas" "area_lugar"  "explicacion" "enlace"

Separar columnas ✂️

Actualmente, la columa coordenadas tiene tres tipos de información distinta: las coordenadas en formato grados-minutos-segundos, la latitud-longitud y el nombre del lugar en inglés (o en su lengua original, en algunos casos).

## # A tibble: 67 x 1
##    coordenadas                                                                  
##    <chr>                                                                        
##  1 30°34′2″N 47°46′6″E / 30.56722, 47.76833 (Shatt al Arab Hotel)               
##  2 27°51′0″N 65°10′0″E / 27.85000, 65.16667 (Predator Air Base)                 
##  3 24°17′21″N 153°58′41″E / 24.28917, 153.97806 (Minami Torishima Airport)      
##  4 24°12′22″N 120°36′26″E / 24.206117, 120.607181 (northwestern Taichung (Ta…   
##  5 25°02′42″N 121°36′12″E / 25.044976, 121.60346 (Nangang, Taipei (Taibei) T…   
##  6 24°12′26″N 120°48′01″E / 24.207203, 120.800278 (Xinshe, Taiwan)              
##  7 24°09′04″N 120°44′11″E / 24.15111, 120.736259 (Taiping, Taiwan)              
##  8 24°11′11″N 120°39′03″E / 24.18626, 120.65074 (Shuinan, Taichung (Taizhong…   
##  9 25°06′27″N 121°32′09″E / 25.107496, 121.535756 (National Security Bureau …   
## 10 25°02′50″N 121°35′29″E / 25.047352, 121.591471 (Army Logistics Command he…   
## # … with 57 more rows

Necesitamos separarlas y para eso utilizaremos funciones del paquete {tidyr}. Como no hay un solo patrón de caracteres dividiendo todas las variables que están dentro de coordenadas, no podemos hacerlo con una sola llamada a separate(). A primera vista, la mejor opción parecía ser utilizar extract() para extraer los datos de latitud, longitud y nombre del lugar; sin embargo, cuando traté de hacerlo durante la clase no funcionó cómo esperaba. Por alguna extraña razón (que después descubriría) ninguna expresión regular lograba coincidir con la latitud y la longitud. Solo funcionó con el nombre del lugar:

no_accesibles <- extract(no_accesibles,
                         coordenadas, 
                         into = "lugar", 
                         regex = "\\((.+)\\)",
                         remove = FALSE)

select(no_accesibles, lugar)
## # A tibble: 67 x 1
##    lugar                                      
##    <chr>                                      
##  1 Shatt al Arab Hotel                        
##  2 Predator Air Base                          
##  3 Minami Torishima Airport                   
##  4 northwestern Taichung (Taizhong) Taiwan    
##  5 Nangang, Taipei (Taibei) Taiwan            
##  6 Xinshe, Taiwan                             
##  7 Taiping, Taiwan                            
##  8 Shuinan, Taichung (Taizhong), Taiwan       
##  9 National Security Bureau Headquarters      
## 10 Army Logistics Command headquarters, Taiwan
## # … with 57 more rows

En los casos en que no se indicaba ningún nombre entre paréntesis, nos quedó un NA, que era lo esperado.

Ahora necesitamos los datos sobre latitud y longitud. Para ello, haremos dos llamadas a separate(). A esta función tenemos que darle como argumentos qué columna queremos separar y cuál es el patrón que las está separando. En la primera llamada a la función separaremos las coordenadas en grados-minutos-segundos del resto, y en la segunda separaremos la latitud de la longitud.

no_accesibles <- separate(no_accesibles,
                          coordenadas,
                          c("coordenadas_gms", "latitud_longitud"), " / ") %>%
  separate(latitud_longitud, 
           c("latitud", "longitud"), 
           ",* ") 

head(no_accesibles)
## # A tibble: 6 x 8
##   pais   coordenadas_gms  latitud longitud lugar  area_lugar explicacion  enlace
##   <chr>  <chr>            <chr>   <chr>    <chr>  <chr>      <chr>        <chr> 
## 1 Irak   30°34′2″N 47°46… 30.567… 47.76833 Shatt… Hotel Sha… Ya no está … [2]   
## 2 Pakis… 27°51′0″N 65°10… 27.850… 65.16667 Preda… Base Aére… Los vehícul… [3]   
## 3 Japón  24°17′21″N 153°… 24.289… 153.978… Minam… Aeropuert… El territor… [4]   
## 4 Taiwán 24°12′22″N 120°… 24.206… 120.607… north… Taichung   Antigua ima… [5]   
## 5 Taiwán 25°02′42″N 121°… 25.044… 121.603… Nanga… Fábrica M… Superposici… [6]   
## 6 Taiwán 24°12′26″N 120°… 24.207… 120.800… Xinsh… Reservas … Dos reserva… [7]

Ahora sí tenemos las variables necesarias. Latitud y longitud están como caracter, así que tenemos que transformarlas a numéricas. Pero antes, quedémonos solo con las variables que nos interesan y descartemos el resto.

no_accesibles <- select(no_accesibles,
                        pais, lugar, latitud, longitud,  nombre_en_espanol = area_lugar, explicacion)

En principio, transformar la latitud y longitud en variables de tipo numérico debería haber sido algo sencillo. Pero ocurrió lo siguiente:

no_accesibles %>%
  mutate(latitud = as.numeric(latitud)) %>%
  mutate(longitud = as.numeric(longitud))
## Warning in mask$eval_all_mutate(dots[[i]]): NAs introducidos por coerción
## # A tibble: 67 x 6
##    pais   lugar      latitud longitud nombre_en_espanol       explicacion       
##    <chr>  <chr>        <dbl>    <dbl> <chr>                   <chr>             
##  1 Irak   Shatt al …    30.6       NA Hotel Shatt al Arab, B… Ya no está censur…
##  2 Pakis… Predator …    27.8       NA Base Aérea Predator[12]​ Los vehículos aér…
##  3 Japón  Minami To…    24.3       NA Aeropuerto de Minami T… El territorio más…
##  4 Taiwán northwest…    24.2       NA Taichung                Antigua imagen de…
##  5 Taiwán Nangang, …    25.0       NA Fábrica Militar 202 de… Superposición oval
##  6 Taiwán Xinshe, T…    24.2       NA Reservas militares de … Dos reservas mili…
##  7 Taiwán Taiping, …    24.2       NA Reservas militares de … Reservas militare…
##  8 Taiwán Shuinan, …    24.2       NA Aeropuerto de Shuinan … Aeropuerto, fotog…
##  9 Taiwán National …    25.1       NA Cuartel General del Bu… Pixelado          
## 10 Taiwán Army Logi…    25.0       NA Sede del cuartel gener… Pixelado          
## # … with 57 more rows

¿Por qué NAs en longitud? R nos advierte que se introdujeron NAs por coerción, pero en esa columna pareciera solo haber números:

no_accesibles$longitud
##  [1] "47.76833"     "65.16667"     "153.97806"    "120.607181"   "121.60346"   
##  [6] "120.800278"   "120.736259"   "120.65074"    "121.535756"   "121.591471"  
## [11] "121.53977"    "125.08853"    "125.071878"   "53.7042972"   "6.04222"     
## [16] "11.53389"     "-7.25917"     "17.54167"     "-3.733313"    "-3.78772"    
## [21] "-1.012094"    "-2.718279"    "-4.503900"    "-16.754688"   "4.291461"    
## [26] "4.315500"     "1.244167"     "4.70944"      "-1.88000"     "-7.2863111"  
## [31] "5.9794139"    "5.3687944"    "4.9348778"    "5.3583667"    "4.4396500"   
## [36] "6.8939194"    "5.9944444"    "5.7034944"    "5.6793944"    "6.4303"      
## [41] "4.4718389"    "5.8771889"    "21.002371"    "-9.20677"     "40.4832472"  
## [46] "95.3283694"   "179.2350778"  "37.46861"     "17.682741"    "17.855496"   
## [51] "18.087294"    "-77.0365500"  "-77.00028"    "-77.06694"    "-76.8290667" 
## [56] "-76.5743444"  "-76.89167"    "-76.4739000"  "-70.5433694"  "-103.79361"  
## [61] "-74.88500"    "-71.52306"    "-75.7600889"  "-118.3408389" "-145.15000"  
## [66] "-81.6978861"  "-74.2641417"

Pero cuando tratamos de hacer la coerción… ¡NAs!

as.numeric(no_accesibles$longitud)
## Warning: NAs introducidos por coerción
##  [1]        NA        NA        NA        NA        NA        NA        NA
##  [8]        NA        NA        NA        NA        NA        NA        NA
## [15]        NA        NA        NA        NA        NA        NA        NA
## [22]        NA        NA        NA        NA        NA        NA        NA
## [29]        NA        NA        NA        NA        NA        NA        NA
## [36]        NA        NA        NA        NA        NA        NA        NA
## [43]        NA        NA  40.48325  95.32837 179.23508        NA        NA
## [50]        NA        NA        NA        NA        NA        NA        NA
## [57]        NA        NA        NA        NA        NA        NA        NA
## [64]        NA        NA -81.69789        NA

Esto es muy extraño, porque en algunos casos sí pudo convertirlos. Durante un momento pensé que esto no iba a resultar y que el ejercicio quedaba hasta aquí. 💔 ¿La mejor opción en momentos como este? Recreo.

¿Qué está pasando acá? Claramente hay por ahí algún caracter no numérico que está haciendo que sea imposible convertir los datos. Y que hizo imposible también usar extract() cuando lo intenté con esas columnas. Primero revisé si no había algo extraño al inicio. El resultado es esperado: tengo dígitos o el signo menos.

str_extract(no_accesibles$longitud, "^(.)")
##  [1] "4" "6" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "1" "5" "6" "1" "-" "1" "-"
## [20] "-" "-" "-" "-" "-" "4" "4" "1" "4" "-" "-" "5" "5" "4" "5" "4" "6" "5" "5"
## [39] "5" "6" "4" "5" "2" "-" "4" "9" "1" "3" "1" "1" "1" "-" "-" "-" "-" "-" "-"
## [58] "-" "-" "-" "-" "-" "-" "-" "-" "-" "-"

Miremos el final:

str_extract(no_accesibles$longitud, ".$")
##  [1] ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  "" 
## [20] ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  ""  "" 
## [39] ""  ""  ""  ""  ""  ""  "2" "4" "8" ""  ""  ""  ""  ""  ""  ""  ""  ""  "" 
## [58] ""  ""  ""  ""  ""  ""  ""  ""  "1" ""

¡¿Y eso?! 😧 Hay una nada misteriosa al final de todas las longitudes excepto de las que sí se pudieron convertir. ¿Será solo el último? Probemos con los dos:

str_extract(no_accesibles$longitud, "..$")
##  [1] "3"  "7"  "6"  "1"  "6"  "8"  "9"  "4"  "6"  "1"  "7"  "3"  "8"  "2"  "2" 
## [16] "9"  "7"  "7"  "3"  "2"  "4"  "9"  "0"  "8"  "1"  "0"  "7"  "4"  "0"  "1" 
## [31] "9"  "4"  "8"  "7"  "0"  "4"  "4"  "4"  "4"  "3"  "9"  "9"  "1"  "7"  "72"
## [46] "94" "78" "1"  "1"  "6"  "4"  "0"  "8"  "4"  "7"  "4"  "7"  "0"  "4"  "1" 
## [61] "0"  "6"  "9"  "9"  "0"  "61" "7"

El penúltimo caracter es un número en todos los casos. Pero la mayoría tiene un caracter misterioso al final. Nuestra situación, entonces, es la siguiente: tenemos 4 valores que terminan con un dígito y 63 con una nada misteriosa. ¿Cómo solucionarlo? Eliminemos la nada misteriosa al final de cada número. Una solución rápida sería borrar el último caracter así:

no_accesibles %>%
  mutate(longitud = str_remove(longitud, ".$"))

El problema es que lo perdería en los cuatro casos que sí son numéricos. Lo que haremos, entonces, será usar if_else dentro de mutate para indicar una condición para la modificación. Nuestra condición será: is.na(str_extract(longitud, "[0-9]$"). Con str_extract() estamos identificando todas las celdas que terminan con un dígito. Las que no terminan en dígito devuelven NA:

str_extract(no_accesibles$longitud, "[0-9]$")
##  [1] NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA 
## [20] NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA  NA 
## [39] NA  NA  NA  NA  NA  NA  "2" "4" "8" NA  NA  NA  NA  NA  NA  NA  NA  NA  NA 
## [58] NA  NA  NA  NA  NA  NA  NA  NA  "1" NA

Al rodear esta búsqueda de is.na(), lo que obtenemos es TRUE para los NA y FALSE para los dígitos. Eso nos permite dar nuestra condición dentro de if_else(): si el resultado es TRUE, removeremos el último caracter (str_remove(longitud, ".$")). Si es FALSE, dejamos todo tal como está:

no_accesibles <- no_accesibles %>%
  mutate(longitud = if_else(is.na(str_extract(longitud, "[0-9]$")), str_remove(longitud, ".$"), longitud)
    ) 

¡Listo! Ahora sí podemos convertir nuestras columnas a valores numéricos 🤞:

no_accesibles <- no_accesibles %>%
  mutate(latitud = as.numeric(latitud)) %>%
  mutate(longitud = as.numeric(longitud))

head(no_accesibles)
## # A tibble: 6 x 6
##   pais   lugar        latitud longitud nombre_en_espanol  explicacion           
##   <chr>  <chr>          <dbl>    <dbl> <chr>              <chr>                 
## 1 Irak   Shatt al Ar…    30.6     47.8 Hotel Shatt al Ar… Ya no está censurada.…
## 2 Pakis… Predator Ai…    27.8     65.2 Base Aérea Predat… Los vehículos aéreos …
## 3 Japón  Minami Tori…    24.3    154.  Aeropuerto de Min… El territorio más ori…
## 4 Taiwán northwester…    24.2    121.  Taichung           Antigua imagen de la …
## 5 Taiwán Nangang, Ta…    25.0    122.  Fábrica Militar 2… Superposición oval    
## 6 Taiwán Xinshe, Tai…    24.2    121.  Reservas militare… Dos reservas militare…

🎉

Todo listo.

El momento emocionante de la clase: el mapa 🗺

Nunca había hecho un mapa en R. Así que hice lo que siempre hago cuando no sé hacer algo: buscar en Google. Tomé un ejemplo de la documentación del paquete {leaflet} que incluía la opción de popup para que apareciera el nombre del lugar al hacer clic, y lo adapté a nuestras variables. Para que apareciera tanto el nombre como la explicación, uní esas dos variables en una sola antes de crear el mapa.

No sé cual es su experiencia vital con R, pero a mí las cosas nunca me funcionan la primera vez que las intento. Así que cuando ejecuté el código y todo funcionó, mi reacción fue:

no_accesibles <- no_accesibles %>% 
  unite("zona", c(nombre_en_espanol, explicacion), sep = ": \n", remove = FALSE, na.rm = TRUE)

leaflet(no_accesibles) %>%
  addTiles() %>%
  addMarkers(lng = ~longitud, lat = ~latitud, popup = ~htmlEscape(zona))

La opción por defecto de Leaflet es trabajar con los mapas de OpenStreetMap. Lo realmente interesante sería utilizar Google Maps. Pero eso lo dejaremos para un futuro post :)

Enigma resuelto: los caracteres misteriosos

Cuando estaba escribiendo este post intenté nuevamente extraer latitud y longitud usando una expresión regular. Y nuevamente fracasé. Pero al menos descubrí qué era lo que pasaba. En algún momento copié y pegué esos valores fuera de un chunk de código y lo que vi fue esto:

¡Eso son los caracteres misteriosos!

Enigma sin resolver: Tantauco 😒

En Sudamérica, el único lugar que aparece en esta lista es Tantauco, un parque nacional privado administrado por la Fundación Futuro, cuyo directorio está conformado por la familia del actual presidente, Sebastián Piñera. ¿Por qué hay una zona cuya imagen satelital no es accesible?¿Qué hay ahí que no podemos ver?