8 votos

¿Cómo unir una tabla a un shapefile con ID y nombres no coincidentes (cadenas similares)?

Tengo un problema molesto para el que intento encontrar una solución automatizada. La versión abreviada es que tengo un shapefile y una tabla de datos creados para las regiones dentro de los países. La tabla de datos creados NO tiene ningún tipo de código GID/admin estandarizado para que coincida con los shapefiles, y los nombres de las regiones tampoco coinciden exactamente. Echemos un vistazo más de cerca; aquí está mi marco de datos ficticio + shapefile.

library(rgdal)

#load in shapefile
arm <- readOGR("D:/Country-Shapefiles/ARM_adm_shp", layer = "ARM_adm1")

#create dummy data frame
id <- c(100:110)
name <- c("Aragatsotn", "Ararat", "Armavir", "Gaghark'unik'", "Kotayk", "Lorri", 
          "Shirak", "Syunik'", "Tavush", "Vayots' Dzor", "Yerevan City")
value <- runif(11, 0.0, 1.0)
df <- data.frame(id, name, value)

Así que lo que tengo es una tabla con IDs aparentemente aleatorios, nombres de regiones, y un valor para ser trazado con un mapa choropleth. Se parece a esto:

> df
    id          name     value
1  100    Aragatsotn 0.6923852
2  101        Ararat 0.5762024
3  102       Armavir 0.4688358
4  103 Gaghark'unik' 0.4702253
5  104        Kotayk 0.9347992
6  105         Lorri 0.1937813
7  106        Shirak 0.5162604
8  107       Syunik' 0.4332389
9  108        Tavush 0.9889513
10 109  Vayots' Dzor 0.2182024
11 110  Yerevan City 0.5791886

Mirando los atributos shapefile de interés, tenemos esto:

> arm@data[c("ID_1", "NAME_1")]

       ID_1      NAME_1
    0     1  Aragatsotn
    1     2      Ararat
    2     3     Armavir
    3     4      Erevan
    4     5 Gegharkunik
    5     6      Kotayk
    6     7        Lori
    7     8      Shirak
    8     9      Syunik
    9    10      Tavush
    10   11 Vayots Dzor

Idealmente, df incluiría algún tipo de ID de administrador coincidente para unirlo al shapefile. Lamentablemente, quien creó los datos que estoy utilizando no siguió estas convenciones. Otra posibilidad sería hacer coincidir los nombres de las regiones... pero, como puede ver, hay ligeras variaciones en cada nombre.

El emparejamiento a mano siempre es una solución de reserva, pero ¿quién quiere tomarse el tiempo de hacerlo? ;) Pero en realidad, incluso excluyendo la pereza, el proyecto en el que estoy trabajando va a mapear docenas y docenas de países diferentes, así que estoy buscando una solución automatizada que pueda hacerlo todo sin tener que hacer nada a mano. ¿Es esto posible? ¿Puedo hacer coincidir de algún modo estos nombres -casi- de regiones con los shapefiles?

Sidenote: I'm looking into grepl para coincidencias parciales de cadenas por esta entrada pero no estoy seguro de que sea una buena solución, ya que tendré que extraer los nombres de las columnas en lugar de introducir cada nombre de región a mano.

EDIT : Cuando comparo los ID a mano, lo que hago es crear una nueva columna en mi marco de datos y añadir los términos que coinciden exactamente con los del shapefile. Lamentablemente, debido a las peculiaridades de los datos, el orden de los nombres tampoco coincide, por lo que sigue siendo necesario introducirlos manualmente. Espero algún tipo de solución completamente automatizada (si es que es posible).

0 votos

Si tienes suerte y tienes el mismo número de registros en el mismo orden tanto en el shapefile como en la tabla, puedes copiar y pegar los nombres en columnas contiguas de una nueva tabla, unirla al shapefile utilizando sus nombres y unirla a la tabla utilizando sus nombres. (O usando una copia de tu shapefile pegar los nombres de la tabla directamente en su dbf en una hoja Excel o Libre/Open Office anterior a 2007). Si no tienes un número exacto de registros uno a uno sino muchos "tramos" largos de ellos puedes mezclar un poco de trabajo manual con copiar y pegar.

0 votos

Esto es lo que acabé haciendo manualmente a mano, pero por desgracia no están en el orden correcto. Incluso si se ordenan alfabéticamente, puede que no funcionen siempre (en este ejemplo, Erevan = Yerevan City, lo que desordena el resto de la lista).

6voto

Farid Cher Puntos 5306

Me decantaría por stringdist que ha implementado muchos algoritmos para calcular la similitud parcial (distancia) de cadenas, incluyendo Jaro-winkler . Aquí tiene una solución rápida:

  #df to be joined
  id <- c(100:111)
  name <- c("Aragatsotn", "Ararat", "Armavir", "Gaghark'unik'", "Kotayk", "Lorri", 
            "Shirak", "Syunik'", "Tavush", "Vayots' Dzor", "Yerevan City","Aragatsotn")
  value <- runif(12, 0.0, 1.0)
  df <- data.frame(id, name, value)

  #create shape data df
  shpNames <- c("Aragatsotn",
               "Ararat",
               "Armavir",
               "Erevan",
               "Gegharkunik",
               "Kotayk",
               "Lori",
               "Shirak",
               "Syunik",
               "Tavush",
               "VayotsDzor")
  arm.data  <- data.frame(ID_1=1:11,NAME_1=shpNames)

  #simple match (only testing)
  match(df$name,arm.data$NAME_1)
  #simple merge (testing)
  merge(arm.data,df,by.x="NAME_1",by.y="name",all.x=TRUE)

  #partial match using stringdist package
  library("stringdist")
  am<-amatch(arm.data$NAME_1,df$name,maxDist = 3)
  b<-data.frame()
  for (i in 1:dim(arm.data)[1]) {
      b<-rbind(b,data.frame(arm.data[i,],df[am[i],]))
  }
  b

sale:

ID_1      NAME_1  id          name     value
1     1  Aragatsotn 100    Aragatsotn 0.8510984
2     2      Ararat 101        Ararat 0.3004329
3     3     Armavir 102       Armavir 0.9258740
4     4      Erevan  NA          <NA>        NA
5     5 Gegharkunik 103 Gaghark'unik' 0.9935353
6     6      Kotayk 104        Kotayk 0.6025050
7     7        Lori 105         Lorri 0.9577662
8     8      Shirak 106        Shirak 0.6346550
9     9      Syunik 107       Syunik' 0.6531175
10   10      Tavush 108        Tavush 0.9726032
11   11  VayotsDzor 109  Vayots' Dzor 0.3457315

Puedes jugar con el parámetro maxDist del método amatch. Aunque 3 funciona mejor con tus datos de muestra.

0 votos

Sí, ha funcionado en mi ejemplo. Ahora tengo que probar algunos más. Pregunta relacionada: ¿cómo puedo conseguir esta misma unión manteniendo el shapefile espacial? Parece que este trozo de código acaba de crear un marco de datos con los datos unidos, pero todavía tengo que ser capaz de asignarlos.

0 votos

He creado el marco de datos manualmente para que tu problema sea reproducible. Cuando lees un shapefile a través de readOGR, la clase de salida sería una de las clases derivadas de "sp" como "SpatialPointsDataFrame". Y todos ellos tienen un atributo "data" que contiene todos los datos de atributos que es de tipo dataframe. En mi ejemplo me estoy uniendo a la dataframe y la información geométrica no se tocan. Así que para su ejemplo, basta con cambiar arm.data à arm@data y funcionaría bien.

0 votos

No utilizar arm@data Eso crearía un gran lío (registros que no coinciden con sus geometrías correctas).

6voto

SteveBurkett Puntos 960

Quiero añadir algunos detalles a la respuesta de Farid Cher, ya que se trata de un problema muy común. Utilizando amatch puede hacer maravillas, pero con estos Spatial objetos que debe no utilice base::merge y no acceder a la @data ranura. Eso llevaría inevitablemente a un lío terrible ( base::merge cambia el orden de los registros, y ya no coincidirían con las geometrías).

En su lugar, utilice la función sp::merge utilizando el método SpatialPolygonsDataFrame como primer argumento en merge . También hay que tener en cuenta el posible problema de tener registros duplicados. Y he añadido datos para que el ejemplo sea autónomo y reproducible.

library(raster)
#example data.frame
name <- c("Aragatsotn", "Ararat", "Armavir", "Gaghark'unik'", "Kotayk", "Lorri", "Shirak", "Syunik'", "Tavush", "Vayots' Dzor", "Yerevan City","Aragatsotn")
value <- runif(12, 0.0, 1.0)
df <- data.frame(name, value)

# example SpatialPolygonsDataFrame
arm <- getData('GADM', country='ARM', level=1)[, c('NAME_1')]

Este

merge(arm, df, by.x='NAME_1', by.y='name')

falla con el mensaje

#Error in .local(x, y, ...) : non-unique matches detected

Porque hay dos registros de "Aragatsotn" en df . Podrías hacer

merge(arm, df, by.x='NAME_1', by.y='name', duplicateGeoms=TRUE)

Pero normalmente lo más sensato es utilizar algo como

df <- aggregate(df[, 'value', drop=FALSE], df[, 'name', drop=FALSE], mean)
m <- merge(arm, df, by.x='NAME_1', by.y='name')
data.frame(m)

data.frame(m)
#        NAME_1       value
#1   Aragatsotn 0.421576186
#2       Ararat 0.003138734
#3      Armavir 0.703402672
#4       Erevan          NA
#5  Gegharkunik          NA
#6       Kotayk 0.926883799
#7         Lori          NA
#8       Shirak 0.430585540
#9       Syunik          NA
#10      Tavush 0.121784395
#11 Vayots Dzor          NA

Ahora bien, la fusión no funciona bien en este caso porque los nombres no coinciden. Así que puede utilizar

i <- amatch(df$name, arm$NAME_1, maxDist = 3)
df$match[!is.na(i)] <- arm$NAME_1[i[!is.na(i)]]
df
#            name       value       match
#1     Aragatsotn 0.421576186  Aragatsotn
#2         Ararat 0.003138734      Ararat
#3        Armavir 0.703402672     Armavir
#4  Gaghark'unik' 0.682169824 Gegharkunik
#5         Kotayk 0.926883799      Kotayk
#6          Lorri 0.128894086        Lori
#7         Shirak 0.430585540      Shirak
#8        Syunik' 0.163562936      Syunik
#9         Tavush 0.121784395      Tavush
#10  Vayots' Dzor 0.383439033 Vayots Dzor
#11  Yerevan City 0.168033419        <NA>

Ya casi está, pero "Yerevan City" no coincide con "Erevan". En este caso, puede aumentar maxDist

i <- amatch(df$name, arm$NAME_1, maxDist = 10)
df$match[!is.na(i)] <- arm$NAME_1[i[!is.na(i)]]

Pero el aumento maxDist no siempre funcionará o dará resultados erróneos, ya que los nombres de las variantes pueden ser muy distintos. Así que en muchos casos tendrá que hacer algunas sustituciones manuales como:

df[df$name=="Yerevan City", 'match'] <- "Erevan"

En ambos casos seguido de

m <- merge(arm, df, by.x='NAME_1', by.y='match')

En cualquier caso, deberá comprobar si sum(table(i) > 1) == 0 ; aunque merge debería fallar de todos modos si hay coincidencias duplicadas.

0 votos

Bonitos detalles Por eso llamé a mi respuesta rápido . Sin embargo, el marco de datos coincidente (df) no contendría los datos geométricos, ¿verdad? El OP quiere mapear el df unido. La agregación espacial en lugar de la agregación por atributos sería otra alternativa para los casos de uniones múltiples.

0 votos

Df no tiene geometrías, de ahí el paso final con merge . El agregado espacial es útil para diferentes casos (si, en este ejemplo, NAME_1 tenía duplicados).

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X