Our adventurous clients often go on daring survey missions to collect data in remote locations, armed with GPS devices to collect latitude and longitude coordinates, pinpointing the exact spots where face-to-face interviews go down. Awesome, right? Yes, but danger lurks in the shadows – the risk of re-identification! Fear not, fellow explorers, for we have the ultimate solution to save the day! Jitter it!

This post will show how to minimize this risk by displacing coordinates using the geographic displacement procedures developed by our friends at ICF International for the DHS project. This procedure aims to balance the need to protect respondent confidentiality with the need to make available to the public analytically useful data.

The procedure involves randomly displacing the GPS latitude/longitude positions for all surveys randomly, so that:

  • Urban clusters have a minimum of 0 and a maximum of 2 kilometers of error.
  • Rural clusters have a minimum of 0 and a maximum of 5 kilometers of positional error with a further 1% of the rural clusters displaced a minimum of 0 and a maximum of 10 kilometers.

 

  1. Setup environment:

The code begins by loading necessary R packages using the pacman package management tool. It imports various packages such as dplyr, maps, ggplot2, spatstat, and others that are required for data manipulation, visualization, and spatial operations.

if (!require("pacman")) install.packages("pacman")
library ("pacman")
pacman::p_load(dplyr, maps, ggplot2, maptools, raster, rgdal, mapproj,
spatstat, rgeos, splancs, fields, geosphere, tibble, labelled, rmarkdown)

 

  1. Generate random data within Ghana:

We then generate a random GPS dataset with longitude and latitude coordinates to demonstrate the procedure. For the sake of this demonstration, we select Ghana. The code below creates a dataframe named mydata with columns for latitude and longitude. The runif() function is used to generate random values for latitude and longitude within specified ranges.

n <- 100
set.seed(123)
lat <- runif(n, min = 6, max = 8)
lon <- runif(n, min = -2, max = 0)
mydata <- as.data.frame(cbind(lat, lon))

  1. Merge displaced GPS into the dataset:

We are now ready to create our first function to merge displaced GPS coordinates into the original dataset. The displace.merger() function takes the displaced GPS object and the column names for latitude and longitude as input. The function creates a new dataframe named mydata.displaced that combines the original dataset and the displaced GPS coordinates. It replaces the original latitude and longitude columns with the displaced values.

displace.merger <- function (displacedGPS, gps.vars) {
displaced.df <- data.frame(coordinates(displacedGPS))
row.names(displaced.df) <- which(complete.cases(mydata[,gps.vars]))
mostattributes(displaced.df$coords.x1) <- attributes(mydata[,gps.vars[1], drop=T])
mostattributes(displaced.df$coords.x2) <- attributes(mydata[,gps.vars[1], drop=T])
mydata.displaced <- left_join(rownames_to_column(mydata),
rownames_to_column(displaced.df),
by = ("rowname"))
mydata.displaced[,gps.vars] <- mydata.displaced[,c("coords.x1", "coords.x2")]
mydata.displaced <- mydata.displaced[!names(mydata.displaced) %in% c("rowname",
"coords.x1",
"coords.x2")]
return (mydata.displaced)
}

 

  1. Geographic displacement procedure based on DHS policy

We next create our workhorse gps displacement function. The displace() function is defined to perform the geographic displacement procedure. It takes the column names for latitude and longitude, administrative boundaries data (admin), the number of samples to generate (samp_num), and the number of points to randomly generate around each GPS coordinate (other_num). The function begins by plotting the original GPS points on a map. It then proceeds to perform the displacement procedure using a series of steps including buffering, intersection with administrative boundaries, and generating random points. The resulting displaced GPS points are merged into the original dataset using the displace.merger() function. The function also includes additional map plots to visualize the displaced GPS points. Finally, it returns the displaced dataset. This function is essentially an adaptation to R from the original Python code from Brendan Collis (Blue Raster) and released by DHS: https://dhsprogram.com/pubs/pdf/SAR7/SAR7.pdf.

The function is a bit lengthy so we are not going to post it here, but you can find it in our Github.

  1. Load Goespatial datafiles

We now load the necessary map data using the map_data() function. We specifically filter the data to focus on Ghana by specifying the region as “Ghana”. Next, we obtain the administrative boundaries data for Ghana. In the commented line of code, there is an alternative method using the raster::getData() function, which allows you to retrieve the country map based on the standard 2-letter country codes directly from the UC Davis server. However be advised that this server is often down, so we like to download the .rds file and then source it locally using the readRDS() function to read the administrative boundaries data directly from a file named “gadm36_GHA_0_sp.rds”. This file contains spatial information for the national boundaries of Ghana, and is used by the displace() function to ensure that jittered points don’t go beyond the national boundaries. This procedure can be specified at lower administrative levels if necessary by downloading the requisite .rds file.

countrymap <- map_data("world") %>% filter(region=="Ghana")  #!!! Select correct country
#admin <- raster::getData("GADM", country="GH", level=0) #!!! Select correct country map using standard 2-letter country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
admin <- readRDS(file="gadm36_GHA_0_sp.rds")

 

  1. Run displacement procedure

Finally, we specify the relevant variables by creating a vector named gps.vars with the column names “lon” and “lat”. It’s important to keep in mind that longitude should always come first, followed by latitude. We then call the displace() function, passing the gps.vars vector, along with other arguments. The displace() function performs a geographic displacement procedure on the GPS data. It takes the administrative boundaries data, stored in the admin variable, as well as the number of samples to generate (samp_num) and the number of points to randomly generate around each GPS coordinate (other_num).

Executing this code may take a few minutes to complete, as the displacement procedure involves multiple steps. It processes the GPS data and generates a new dataset named mydata with the displaced GPS points.

gps.vars <- c("lon", "lat") # !!!Include relevant variables, always longitude first, latitude second.
mydata <- displace(gps.vars, admin=admin, samp_num=1, other_num=100000) # May take a few minutes to process.

[1] “Summary Long/Lat statistics before displacement”

lon                lat

Min.   :-1.97907   Min.   :6.001

1st Qu.:-1.38781   1st Qu.:6.491

Median :-1.00017   Median :6.933

Mean   :-0.97155   Mean   :6.997

3rd Qu.:-0.57727   3rd Qu.:7.511

Max.   :-0.02872   Max.   :7.989

 

The displaced GPS points are shown below in red, with their original location in black.

A closer detail is shown below. As you can see, the displacement is relatively minor, preserving most of the analytical advantages of the GPS data, while introducing enough uncertainty in the original location of the interview to minimize the risk of re-identification.

“Summary Long/Lat statistics after displacement”
lon                lat
Min.   :-1.98104   Min.   :5.995
1st Qu.:-1.40776   1st Qu.:6.506
Median :-1.01086   Median :6.926
Mean   :-0.97139   Mean   :6.996
3rd Qu.:-0.56935   3rd Qu.:7.524
Max.   : 0.01255   Max.   :8.003
“Processing time = 59 seconds”

So remember, when dealing with GPS data along with other sensitive information, better jitter it! This procedure can be useful in various applications where preserving the confidentiality of geospatial data is necessary.

Please note that the code provided in the blog post might require additional modifications or refinements depending on specific use cases and data requirements. It is always recommended to carefully review and adapt the code to fit your specific needs.

Nuestros aventureros clientes a menudo llevan a cabo arriesgadas misiones para recopilar datos en ubicaciones remotas, armados con dispositivos GPS para obtener coordenadas de latitud y longitud, precisando los lugares exactos donde se llevan a cabo las entrevistas cara a cara. Impresionante, ¿verdad? Sin embargo, el peligro acecha en las sombras: ¡el riesgo de reidentificación! Pero no temáis, compañeros exploradores, porque tenemos la solución… ¡¡¡Menéalo!!!

En esta publicación, mostraremos cómo minimizar este riesgo mediante el desplazamiento de coordenadas utilizando los procedimientos de desplazamiento geográfico desarrollados por nuestros amigos de ICF International para el proyecto DHS. Este procedimiento tiene como objetivo equilibrar la necesidad de proteger la confidencialidad de los encuestados con la necesidad de poner a disposición del público datos analíticamente útiles.

El procedimiento implica desplazar aleatoriamente las posiciones de latitud y longitud del GPS para todas las encuestas de manera aleatoria, de modo que la ubicación de las encuestas:

  • Urbanas tengan un error mínimo de 0 y un error máximo de 2 kilómetros..
  • Rurales tengan un error mínimo de 0 y un error máximo de 5 kilómetros, con un 1% adicional de los conglomerados rurales desplazados entre 0 y 10 kilómetros.

 

  1. Configurar el entorno:

El código comienza cargando los paquetes de R necesarios utilizando la herramienta de gestión de paquetes pacman. Importa varios paquetes como dplyr, maps, ggplot2, spatstat y otros que se requieren para la manipulación de datos, la visualización y las operaciones espaciales.

if (!require("pacman")) install.packages("pacman")
library ("pacman")
pacman::p_load(dplyr, maps, ggplot2, maptools, raster, rgdal, mapproj,
spatstat, rgeos, splancs, fields, geosphere, tibble, labelled, rmarkdown)

 

  1. Generar datos aleatorios en Ghana:

A continuación, generamos un conjunto de datos aleatorios de GPS con coordenadas de longitud y latitud para demostrar el procedimiento. Para esta demostración, seleccionamos Ghana. El siguiente código crea un marco de datos llamado “mydata” con columnas para latitud y longitud. La función runif() se utiliza para generar valores aleatorios para latitud y longitud dentro de los rangos especificados.

n <- 100
set.seed(123)
lat <- runif(n, min = 6, max = 8)
lon <- runif(n, min = -2, max = 0)
mydata <- as.data.frame(cbind(lat, lon))

  1. Combinar los GPS desplazados en el conjunto de datos:

Ahora estamos listos para crear nuestra primera función para combinar las coordenadas de GPS desplazadas en el conjunto de datos original. La función “displace.merger()” toma como entrada el objeto de GPS desplazado y los nombres de columna para latitud y longitud. La función crea un nuevo marco de datos llamado “mydata.displaced” que combina el conjunto de datos original con las coordenadas de GPS desplazadas. Reemplaza las columnas originales de latitud y longitud con los valores desplazados.

displace.merger <- function (displacedGPS, gps.vars) {
displaced.df <- data.frame(coordinates(displacedGPS))
row.names(displaced.df) <- which(complete.cases(mydata[,gps.vars]))
mostattributes(displaced.df$coords.x1) <- attributes(mydata[,gps.vars[1], drop=T])
mostattributes(displaced.df$coords.x2) <- attributes(mydata[,gps.vars[1], drop=T])
mydata.displaced <- left_join(rownames_to_column(mydata),
rownames_to_column(displaced.df),
by = ("rowname"))
mydata.displaced[,gps.vars] <- mydata.displaced[,c("coords.x1", "coords.x2")]
mydata.displaced <- mydata.displaced[!names(mydata.displaced) %in% c("rowname",
"coords.x1",
"coords.x2")]
return (mydata.displaced)
}

 

  1. Procedimiento de desplazamiento geográfico basado en la política DHS

Procedimiento de desplazamiento geográfico basado en la política DHS: A continuación, creamos nuestra función principal de desplazamiento de GPS. La función “displace()” se define para realizar el procedimiento de desplazamiento geográfico. Toma como argumentos los nombres de columna para latitud y longitud, los datos de límites administrativos (admin), el número de muestras a generar (samp_num) y el número de puntos a generar aleatoriamente alrededor de cada coordenada de GPS (other_num). La función comienza trazando los puntos de GPS originales en un mapa. Luego, procede a realizar el procedimiento de desplazamiento utilizando una serie de pasos que incluyen el búfer, la intersección con los límites administrativos y la generación de puntos aleatorios. Los puntos de GPS desplazados resultantes se combinan con el conjunto de datos original utilizando la función “displace.merger()”. La función también incluye gráficos adicionales para visualizar los puntos de GPS desplazados. Finalmente, devuelve el conjunto de datos desplazado. Esta función es una adaptación en R del código original en Python de Brendan Collis (Blue Raster) y publicado por DHS: https://dhsprogram.com/pubs/pdf/SAR7/SAR7.pdf.

La función es un poco extensa, por lo que no la vamos a incluir aquí, pero puedes encontrarla en nuestro Github.

  1. Cargar archivos de datos geoespaciales

Cargamos los datos del mapa necesarios utilizando la función map_data(). Específicamente filtramos los datos para centrarnos en Ghana, especificando la región como “Ghana”. A continuación, obtenemos los datos de los límites administrativos de Ghana. En la línea de código comentada, hay un método alternativo que utiliza la función raster::getData(), la cual te permite obtener el mapa del país basado en los códigos de país de 2 letras estándar directamente desde el servidor de UC Davis. Sin embargo, ten en cuenta que este servidor a menudo está fuera de servicio, por lo que preferimos descargar el archivo .rds y luego cargarlo localmente utilizando la función readRDS() para leer los datos de los límites administrativos directamente desde un archivo llamado “gadm36_GHA_0_sp.rds”. Este archivo contiene información espacial sobre los límites nacionales de Ghana y es utilizado por la función displace() para asegurar que los puntos desplazados no se encuentren más allá de los límites nacionales. Este procedimiento puede especificarse en niveles administrativos inferiores si es necesario, descargando el archivo .rds correspondiente.

countrymap <- map_data("world") %>% filter(region=="Ghana")  #!!! Select correct country
#admin <- raster::getData("GADM", country="GH", level=0) #!!! Select correct country map using standard 2-letter country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
admin <- readRDS(file="gadm36_GHA_0_sp.rds")

 

  1. Ejecutar el proceso de desplazamiento

Finalmente, especificamos las variables relevantes creando un vector llamado gps.vars con los nombres de columna “lon” y “lat”. Es importante tener en cuenta que la longitud siempre debe ir primero, seguida de la latitud. Luego llamamos a la función displace(), pasando el vector gps.vars junto con otros argumentos. La función displace() realiza un procedimiento de desplazamiento geográfico en los datos de GPS. Toma los datos de los límites administrativos, almacenados en la variable admin, así como el número de muestras a generar (samp_num) y el número de puntos a generar aleatoriamente alrededor de cada coordenada de GPS (other_num).

Ejecutar este código puede llevar algunos minutos en completarse, ya que el procedimiento de desplazamiento implica múltiples pasos. Procesa los datos de GPS y genera un nuevo conjunto de datos llamado mydata con los puntos de GPS desplazados.

gps.vars <- c("lon", "lat") # !!!Include relevant variables, always longitude first, latitude second.
mydata <- displace(gps.vars, admin=admin, samp_num=1, other_num=100000) # May take a few minutes to process.

[1] “Summary Long/Lat statistics before displacement”

lon                lat

Min.   :-1.97907   Min.   :6.001

1st Qu.:-1.38781   1st Qu.:6.491

Median :-1.00017   Median :6.933

Mean   :-0.97155   Mean   :6.997

3rd Qu.:-0.57727   3rd Qu.:7.511

Max.   :-0.02872   Max.   :7.989

 

Los puntos de GPS desplazados se muestran a continuación en rojo, con su ubicación original en negro.

A continuación mostramos un detalle más cercano. Como puedes ver, el desplazamiento es relativamente menor, preservando la mayoría de las ventajas analíticas de los datos de GPS, al tiempo que introduce suficiente incertidumbre en la ubicación original de la entrevista para minimizar el riesgo de reidentificación.

“Summary Long/Lat statistics after displacement”
lon                lat
Min.   :-1.98104   Min.   :5.995
1st Qu.:-1.40776   1st Qu.:6.506
Median :-1.01086   Median :6.926
Mean   :-0.97139   Mean   :6.996
3rd Qu.:-0.56935   3rd Qu.:7.524
Max.   : 0.01255   Max.   :8.003
“Processing time = 59 seconds”

Este procedimiento puede ser útil en diversas aplicaciones donde sea necesario preservar la confidencialidad de los datos geoespaciales. Así que recuerda, al tratar con datos de GPS junto con otra información confidencial siempre te queda una opción… ¡¡¡Menéalo!!!

 

Ten en cuenta que el código proporcionado en la publicación del blog podría requerir modificaciones o refinamientos adicionales según los casos de uso específicos y los requisitos de los datos. Siempre se recomienda revisar y adaptar cuidadosamente el código para que se ajuste a tus necesidades específicas.


Sobre Rosan International

ROSAN es una empresa tecnológica especializada en el desarrollo de soluciones de Data Science e Inteligencia Artificial con el objetivo de ayudar a resolver algunos de los más desafiantes retos globales. Contáctanos para descubrir cómo podemos ayudarte a extraer la información más valiosa de tus datos y optimizar tus procesos.