Wie verbinde ich eine Tabelle mit einem Shapefile mit nicht übereinstimmenden IDs und Namen (ähnliche Zeichenfolgen)?

8

Ich habe ein nerviges Problem, für das ich eine automatisierte Lösung finden möchte. Die Kurzfassung ist, dass ich ein Shapefile und eine Tabelle mit erstellten Daten für Regionen innerhalb von Ländern habe. Die erstellte Datentabelle enthält KEINE standardisierten GIDs / Admin-Codes, die mit Shapefiles übereinstimmen, und die Regionsnamen stimmen auch nicht genau überein. Lasst uns genauer hinschauen; Hier ist mein Dummy-Datenrahmen + 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)

Ich habe also eine Tabelle mit scheinbar zufälligen IDs, Regionsnamen und einem Wert, der mit einer Choroplethenkarte aufgezeichnet werden soll. Sieht aus wie das:

> 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

Wenn wir uns die interessanten Shapefile-Attribute ansehen, haben wir Folgendes:

> 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

Idealerweise dfwürde eine Art übereinstimmender Administrator-IDs enthalten sein, um sie mit dem Shapefile zu verbinden. Wer auch immer die von mir verwendeten Daten erstellt hat, hat diese Konventionen leider nicht befolgt. Alternativ wäre es großartig, die Regionsnamen selbst abzugleichen ... aber wie Sie sehen können, gibt es bei jedem Namen geringfügige Abweichungen.

Matching per Hand ist immer eine Backup-Lösung, aber wer möchte sich die Zeit dafür nehmen? ;) Aber abgesehen von Faulheit wird das Projekt, an dem ich arbeite, Dutzende und Dutzende verschiedener Länder abbilden. Deshalb suche ich nach einer automatisierten Lösung, die alles kann, ohne etwas von Hand tun zu müssen. Ist das möglich? Kann ich diese fast Regionsnamen irgendwie mit den Shapefiles abgleichen?

Nebenbemerkung: Ich suche nach Teilzeichenfolgenübereinstimmungen greplfür diesen Beitrag , bin mir aber nicht sicher, ob dies eine mögliche Lösung ist, da ich aus den Spaltennamen ziehen muss, anstatt jeden Regionsnamen manuell einzugeben .

BEARBEITEN: Wenn ich die IDs von Hand abgleichen möchte, habe ich eine neue Spalte in meinem Datenrahmen erstellt und die genau übereinstimmenden Begriffe aus dem Shapefile hinzugefügt. Aufgrund der Besonderheiten der Daten stimmt die Reihenfolge der Namen leider auch nicht überein, sodass noch einige manuelle Eingaben erforderlich sind. Ich hoffe auf eine vollständig automatisierte Lösung (wenn es überhaupt möglich ist).

Lauren
quelle
Wenn Sie Glück haben und sowohl im Shapefile als auch in der Tabelle dieselbe Anzahl von Datensätzen in derselben Reihenfolge haben, können Sie die Namen kopieren und in benachbarte Spalten in einer neuen Tabelle einfügen, diese mit ihren Namen mit dem Shapefile verknüpfen und diese mit verknüpfen die Tabelle mit ihren Namen. (Oder verwenden Sie eine Kopie Ihres Shapefiles, und fügen Sie die Tabellennamen direkt in die Datenbank in einem Excel- oder Libre / Open Office-Blatt vor 2007 ein.) Wenn Sie keine genaue Anzahl von Eins-zu-Eins-Datensätzen, sondern viele lange "Strecken" haben. von ihnen können Sie ein bisschen manuelle Arbeit mit Kopieren und Einfügen mischen.
Johns
Dies ist, was ich manuell von Hand gemacht habe, aber leider sind sie nicht in der richtigen Reihenfolge. Selbst wenn es alphabetisch aufgelistet ist, funktioniert es möglicherweise nicht immer (in diesem Beispiel Erevan = Yerevan City, wodurch der Rest der Liste außer Betrieb gesetzt wird).
Lauren

Antworten:

6

Ich würde mich für ein stringdistPaket entscheiden, das viele Algorithmen implementiert hat, um die partielle Ähnlichkeit (Entfernung) von Strings einschließlich zu berechnen Jaro-winkler. Hier ist eine schnelle Lösung für Sie:

  #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

es gibt aus:

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

Sie können mit dem Parameter maxDist der amatch-Methode spielen. Obwohl 3 am besten mit Ihren Beispieldaten funktioniert!

Farid Cheraghi
quelle
Ja, das hat bei meinem Beispiel funktioniert! Jetzt noch ein paar testen! Verwandte Frage: Wie kann ich dieselbe Verknüpfung erzielen, während das Shapefile räumlich bleibt? Es scheint, dass dieses Codebit gerade einen Datenrahmen mit den verknüpften Daten erstellt hat, aber ich muss ihn trotzdem zuordnen können.
Lauren
Ich habe den Datenrahmen manuell erstellt, damit Ihr Problem reproduzierbar ist. Wenn Sie ein Shapefile über readOGR lesen, ist die Ausgabeklasse eine der "sp" -Derivatklassen wie "SpatialPointsDataFrame". Und alle haben ein "Daten" -Attribut, das alle Attributdaten vom Typ Datenrahmen enthält. In meinem Beispiel verbinde ich mich mit dem Datenrahmen und die geometrischen Informationen bleiben unberührt. Wechseln Sie für Ihr Beispiel einfach arm.datazu arm@dataund es würde gut funktionieren.
Farid Cheraghi
Nicht verwenden arm@data, das würde ein großes Durcheinander verursachen (Datensätze, die nicht ihren korrekten Geometrien entsprechen)
Robert Hijmans
6

Ich möchte Farid Chers Antwort einige Details hinzufügen, da dies ein sehr häufiges Problem ist. Die Verwendung amatchkann Wunder bewirken, aber mit diesen SpatialObjekten sollten Sie den Steckplatz nicht verwenden base::mergeund nicht darauf zugreifen @data. Das würde unweigerlich zu einem schrecklichen Durcheinander führen ( base::mergeändert die Reihenfolge der Datensätze und sie würden nicht mehr mit den Geometrien übereinstimmen).

Verwenden Sie stattdessen die sp::mergeMethode, indem Sie das SpatialPolygonsDataFrameerste Argument in verwenden merge. Beachten Sie auch das potenzielle Problem, dass Datensätze dupliziert wurden. Und ich habe Daten hinzugefügt, damit das Beispiel in sich geschlossen und reproduzierbar ist.

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')]

Diese

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

schlägt mit Nachricht fehl

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

Weil es zwei Datensätze für "Aragatsotn" in gibt df. Du könntest es tun

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

Aber normalerweise besteht der vernünftige Ansatz darin, etwas Ähnliches zu verwenden

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

Jetzt funktioniert das Zusammenführen in diesem Fall nicht gut, da die Namen nicht übereinstimmen. So können Sie verwenden

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>

Fast da, aber "Yerevan City" passte nicht zu "Erevan". In diesem Fall können Sie erhöhenmaxDist

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

Das Erhöhen maxDistfunktioniert jedoch nicht immer oder führt zu falschen Übereinstimmungen, da Variantennamen sehr unterschiedlich sein können. In vielen Fällen werden Sie am Ende einige manuelle Ersetzungen vornehmen, wie z.

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

In beiden Fällen gefolgt von

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

In jedem Fall möchten Sie überprüfen, ob sum(table(i) > 1) == 0; obwohl mergesollte auf jeden Fall fehlschlagen , wenn es doppelte Streichhölzer.

Robert Hijmans
quelle
Schöne Details! Deshalb habe ich meine Antwort schnell angerufen . Der übereinstimmende Datenrahmen (df) würde jedoch die Geometriedaten nicht enthalten. würde es? Das OP möchte den verbundenen df abbilden. Ein räumliches Aggregat anstelle eines Attributaggregats wäre eine weitere Alternative für Fälle mit mehreren Verknüpfungen.
Farid Cheraghi
df hat keine Geometrien, daher der letzte Schritt mit merge. Das räumliche Aggregat ist für verschiedene Fälle nützlich (wenn in diesem Beispiel NAME_1Duplikate vorhanden waren)
Robert Hijmans