Introdução
Os dados podem ser dispostos de diversas maneiras, embora, com o predomínio das notações textuais, a representação tabular seja o modelo pictórico convencional, malgrado o aspecto rudimentar, a despeito do desenvolvimento recente de elementos de composição gráfica integrados aos ambientes de programação, análise e visualização de dados disponíveis para estatísticos e programadores.
Objetivo
A análise de dados espaciais em saúde pública é uma metodologia utilizada para estudo da distribuição geográfica de eventos sanitários, identificação de clusters ou agrupamentos, da qual nos apropriamos de algumas ferramentas para avaliar a dinâmica topológica das suspeitas de contaminação pela COVID-19 em Juiz de Fora.
Recursos
Levantamento divulgado pelas Secretaria de Saúde e Desenvolvimento Social de Juiz de Fora:
https://tribunademinas.com.br/wp-content/uploads/2020/05/001_Informe_TS.pdf.
Última atualização dos dados: 24/4/2020
Bibliotecas
Carregamos as seguintes bibilotecas do R, para adicionar funcionalidades extras:
- tidyverse - para manipulação de dados;
- gridExtra - para ampliar as possibilidades de manipulação gráfica;
- DT - para incluir tabelas em relatórios Rmarkdown;
- leaflet - biblioteca javascript para desenvolvimento de mapas interativos;
- prettydoc - temas adicionais para documentos Rmarkdown;
- googleway - para acesso aos mapas do google;
- htmlwidgets - para salvar cópia local e interativa do mapa no formato html;
- mapview - para salvar instatâneo do mapa no formato png.
Coleta e limpeza de dados
Baixamos o relatório semanal, convertemos o arquivo pdf em txt, removemos os espaços excedentes e selecionamos as linhas que casam com o padrão definido:
url <- "https://tribunademinas.com.br/wp-content/uploads/2020/05/001_Informe_TS.pdf"
pdf_file <- "monitoramentojf"
download.file(
url, pdf_file, mode = "wb")
monitoramento <- pdf_text(pdf_file)
trts <- monitoramento %>%
str_split(pattern = "\n") %>%
unlist() %>%
str_trim() %>%
str_replace_all("\\s{2,}", ";") %>%
str_subset("\\dº")Criação do dataset
Criamos o dataset contendo a relação de territórios socioassistenciais e o total de casos suspeitos notificados:
Coordenadas geográficas
Para extrairmos as coordenadas, utilizamos a rotina de geolocalização fornecida pela API Google Geocoding, após o registro obrigatório de uma API Key que, depois de salva em um ambiente seguro, pode ser acessada programaticamente para varrer a base de dados do Google Maps.
A geocodificação consiste em transformar endereços, compostos pela combinação dos territórios socioassistenciais, fornecidos pelo arquivo csv importado e transformado em um dataframe, com as constantes que definem o município e a unidade da federação:
Resultados da busca
O resultado da busca é armazenado em um objeto de classe lista, estrutura complexa que pode acondicionar diversos tipos de dados. A resposta retorna dois elementos: o primeiro é results, que fornece diversas informações a respeito do endereço geocodificado; o segundo fornece o status da soliciação. Dentre as informações retornadas pelo primeiro elemento, constam o endereço, abreviado e completo, as coordenadas geográficas - latitude e longitude, a precisão do resultado e o tipo de localidade.
Tratamento de erros
Quando as coordenadas são mescladas com a listagem que representa os bairros onde foram notificadas suspeitas de contaminação pela COVID-19, recebemos uma mensagem de erro:
ts <- cbind(ts, do.call(rbind, lapply(coords, geocode_coordinates)))
Error in data.frame(…, check.names = FALSE) :
arguments imply differing number of rows: 147, 151
Investigação
Investiguemos o motivo da discrepância.
Primeiro, vejamos como se classificam as estruturas de dados:
## [1] "data.frame"
## [1] "list"
## [1] "data.frame"
Depois, qual o tamanho:
## [1] 147
## [1] 147
## [1] 151
Embora o resultado da pesquisa das coordenadas geográficas, armazenado no objeto de classe lista, contenha o mesmo tamanho do objeto onde estão registradas as localidades, a transformação em dataframe resulta em quantidade diferente de registros.
As pistas começam pelos componentes, que podem ser visualizados de forma suscinta:
## List of 2
## $ results:'data.frame': 1 obs. of 6 variables:
## ..$ access_points :List of 1
## .. ..$ : list()
## ..$ address_components:List of 1
## .. ..$ :'data.frame': 4 obs. of 3 variables:
## ..$ formatted_address : chr "Centro, Juiz de Fora - MG, Brazil"
## ..$ geometry :'data.frame': 1 obs. of 4 variables:
## .. ..$ bounds :'data.frame': 1 obs. of 2 variables:
## .. ..$ location :'data.frame': 1 obs. of 2 variables:
## .. ..$ location_type: chr "APPROXIMATE"
## .. ..$ viewport :'data.frame': 1 obs. of 2 variables:
## ..$ place_id : chr "ChIJHYr1vKGcmAARbKiIYVUHpGE"
## ..$ types :List of 1
## .. ..$ : chr [1:3] "political" "sublocality" "sublocality_level_1"
## $ status : chr "OK"
Ou detalhada:
## List of 2
## $ results:'data.frame': 1 obs. of 6 variables:
## ..$ access_points :List of 1
## .. ..$ : list()
## ..$ address_components:List of 1
## .. ..$ :'data.frame': 4 obs. of 3 variables:
## .. .. ..$ long_name : chr [1:4] "Centro" "Juiz de Fora" "Minas Gerais" "Brazil"
## .. .. ..$ short_name: chr [1:4] "Centro" "Juiz de Fora" "MG" "BR"
## .. .. ..$ types :List of 4
## .. .. .. ..$ : chr [1:3] "political" "sublocality" "sublocality_level_1"
## .. .. .. ..$ : chr [1:2] "administrative_area_level_2" "political"
## .. .. .. ..$ : chr [1:2] "administrative_area_level_1" "political"
## .. .. .. ..$ : chr [1:2] "country" "political"
## ..$ formatted_address : chr "Centro, Juiz de Fora - MG, Brazil"
## ..$ geometry :'data.frame': 1 obs. of 4 variables:
## .. ..$ bounds :'data.frame': 1 obs. of 2 variables:
## .. .. ..$ northeast:'data.frame': 1 obs. of 2 variables:
## .. .. .. ..$ lat: num -21.7
## .. .. .. ..$ lng: num -43.3
## .. .. ..$ southwest:'data.frame': 1 obs. of 2 variables:
## .. .. .. ..$ lat: num -21.8
## .. .. .. ..$ lng: num -43.4
## .. ..$ location :'data.frame': 1 obs. of 2 variables:
## .. .. ..$ lat: num -21.8
## .. .. ..$ lng: num -43.3
## .. ..$ location_type: chr "APPROXIMATE"
## .. ..$ viewport :'data.frame': 1 obs. of 2 variables:
## .. .. ..$ northeast:'data.frame': 1 obs. of 2 variables:
## .. .. .. ..$ lat: num -21.7
## .. .. .. ..$ lng: num -43.3
## .. .. ..$ southwest:'data.frame': 1 obs. of 2 variables:
## .. .. .. ..$ lat: num -21.8
## .. .. .. ..$ lng: num -43.4
## ..$ place_id : chr "ChIJHYr1vKGcmAARbKiIYVUHpGE"
## ..$ types :List of 1
## .. ..$ : chr [1:3] "political" "sublocality" "sublocality_level_1"
## $ status : chr "OK"
No presente caso, os dados fornecem informações variadas sobre a entidade político-administrativa pesquisada, dentre as quais coordenadas individualizadas, no formato GMS; contudo, não é o que acontece de maneira inequívoca em todos os itens da lista. Em termos matemáticos, temos: f(x)=y, que é a representação da correspondência unívoca entre os elementos de x, o conjunto dos endereços e y, o conjunto das coordenadas, que vai ser alcançada após a solução dos problemas indicados logo adiante:
## [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1
## [75] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [112] 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 1 1
Podemos identificar qual localidade gerou mais de uma coordenada:
## [1] 66 122 138 142
Além do problema da ambiguidade, há também o problema da precisão, pois nem todas as localidades foram identificadas pela API do Google. Bairros e distritos são identificados como entidades politico-administrativas armazenadas no campo types e rotuladas como political ou locality.
Portanto, para apurar o nível de precisão, primeiro extraímos as entidades discriminadas fora do escopo adequado, restringimos o espaço de busca para bairro ou distrito somente e armazenamos à parte as localidades que receberam respostas dissonantes:
a <- as.vector(map(coords, ~.x[[c(1,2,1,3,1,1)]]) %>% { which(. != "political") } )
b <- as.vector(map(coords, ~.x[[c(1,2,1,3,1,1)]]) %>% { which(. == "locality") } )
c <- setdiff(a,b)
ts$ID <- seq.int(nrow(ts))
newts <- ts[ts$ID %in% c, ]
ts <- ts[!ts$ID %in% c, ]
ts <- select(ts,-c(ID))
newts <- select(newts,-c(ID)) Convergência de dados
Uma vez alcançada a convergência dos dados, à medida em que a quantidade de territórios assistenciais corresponde à quantidade de coordenadas fornecidas pela API , executamos novamente a rotina de geocodificação:
coords <- apply(ts, 1, function(x) {
google_geocode(address = paste(x["Territorios"], "Juiz de Fora", "Minas Gerais", sep = ", "),
key = key)
})Recriamos a base de dados com as informações acopladas:
Acrescentamos as coordenadas obtidas manualmente:
newts$lat <- c(-21.6899653,
-21.725522,
-21.732735,
-21.7094508,
-21.7365873,
-21.7507563,
-21.7596452,
-21.7477443,
-21.7760539,
-21.760832,
-21.7539435,
-21.6884273,
-21.8091857,
-21.8033034,
-21.7458361,
-21.7663093,
-21.6884273,
-21.7244679,
-21.7304045,
-21.660857,
-21.784608,
-21.6989899
)
newts$lng <- c(-43.4888367,
-43.347370,
-43.368339,
-43.4536946,
-43.3453334,
-43.3938828,
-43.6095267,
-43.3489134,
-43.3705015,
-43.345598,
-43.387648,
-43.4443078,
-43.5723056,
-43.3628063,
-43.4118137,
-43.3681126,
-43.4443078,
-43.5419412,
-43.3637777,
-43.387931,
-43.308757,
-43.524908
)
ts<- rbind(ts, newts)
ts <- ts[order(-ts$Casos),]
row.names(ts) <- NULLEfetuamos os ajustes finais:
Transposição
Transportamos as coordenadas para o mapa.
Passe o mouse sobre o ícone para exibir o nome da localidade e clique para exibir a quantidade de notificações:
Agrupamento
Agrupamentos automáticos são exibidos como círculos coloridos no mapa, segundo um vetor de cores, formado pelo verde, amarelo e vermelho, que determina o volume de itens do cluster. O nível de agrupamento é inversamente proporcional ao tamanho do zoom. Quanto maior o zoom menor o cluster, até chegar à unidade de análise individual. Ao passar o mouse sobre o círculo, será exibida a extensão da área delimitada pelos marcadores:
Cópia interativa
Por fim, gravamos cópia local dos mapas, sem perder a interatividade:
saveWidget(widget = m1, file = 'm1.html', selfcontained = T)
saveWidget(widget = m2, file = 'm2.html', selfcontained = T)
mapshot(m1, file = 'm1.png')
mapshot(m2, file = 'm2.png')Referências:
Oldham, Paul. 2018. Exploring Geocoding Scientific Literature with R.
https://www.pauloldham.net/geocoding-scientific-literature-with-r/.
Johnson, Hansen. 2018. Interactive maps in R (with leaflet).
https://hansenjohnson.org/post/interactive-maps-in-r/
Brito, Robison. 2016. Como utilizar a Google Geocoding API para obter endereços.
https://www.devmedia.com.br/como-utilizar-a-google-geocoding-api-para-obter-enderecos/36751