Bagaimana cara bergabung dengan tabel ke shapefile dengan ID dan nama yang tidak cocok (string serupa)?

8

Saya mengalami masalah yang menjengkelkan dan saya mencoba mencari solusi otomatis untuk. Versi singkatnya adalah saya memiliki shapefile dan tabel data yang dibuat untuk wilayah di dalam negara. Tabel data yang dibuat TIDAK memiliki semacam kode GID / admin standar untuk dicocokkan dengan shapefile, dan nama wilayah juga tidak sama persis. Mari kita lihat lebih dekat; inilah bingkai data dummy + shapefile saya.

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)

Jadi yang saya miliki adalah tabel dengan ID acak, nama wilayah, dan nilai yang akan diplot dengan peta choropleth. Terlihat seperti ini:

> 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

Melihat atribut shapefile yang menarik, kami mendapatkan ini:

> 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

Idealnya, dfakan menyertakan semacam ID admin yang cocok untuk bergabung ke shapefile. Sayangnya, siapa pun yang membuat data yang saya gunakan tidak mengikuti konvensi ini. Atau, akan sangat bagus untuk mencocokkan nama wilayah itu sendiri ... tetapi seperti yang Anda lihat, ada sedikit variasi dalam setiap nama.

Mencocokkan dengan tangan selalu merupakan solusi cadangan, tetapi siapa yang mau meluangkan waktu untuk melakukan itu? ;) Tapi sungguh, bahkan mencegah kemalasan, proyek yang sedang saya kerjakan akan memetakan lusinan negara, jadi saya mencari solusi otomatis yang dapat melakukan segalanya tanpa harus melakukan apa pun dengan tangan. Apakah ini mungkin? Bisakah saya mencocokkan nama-nama wilayah-paling-ini dengan shapefile?

Sidenote: Saya mencari greplkecocokan string parsial untuk setiap posting ini , tapi saya tidak yakin apakah ini solusi potensial karena saya perlu menggambar dari nama kolom daripada memasukkan setiap nama wilayah dengan tangan.

EDIT: Ketika saya mencocokkan ID dengan tangan, apa yang telah saya lakukan adalah membuat kolom baru di bingkai data saya dan menambahkan istilah pencocokan tepat dari shapefile. Sayangnya, karena kekhasan data, urutan nama tidak cocok, jadi ini masih memerlukan beberapa input manual. Saya berharap untuk semacam solusi yang sepenuhnya otomatis (jika itu mungkin).

Lauren
sumber
Jika Anda beruntung dan memiliki jumlah catatan yang sama dalam urutan yang sama di shapefile dan tabel, Anda bisa menyalin dan menempelkan nama-nama itu ke dalam kolom yang bersebelahan di tabel baru, bergabung dengan itu ke shapefile menggunakan nama-namanya, dan bergabung dengan itu untuk tabel menggunakan namanya. (Atau menggunakan salinan shapefile Anda, tempelkan nama tabel langsung ke dbf di lembar Excel atau Libre / Open Office pra-2007.) Jika Anda tidak memiliki jumlah persis satu ke satu catatan tetapi banyak "peregangan" panjang. dari mereka Anda dapat mencampur sedikit pekerjaan manual dengan salinan dan pasta.
hidung belang
Inilah yang akhirnya saya lakukan secara manual dengan tangan, tetapi sayangnya mereka tidak dalam urutan yang benar. Sekalipun terdaftar secara alfabet, masih mungkin tidak berfungsi sepanjang waktu (dalam contoh ini, Erevan = Kota Yerevan, yang membuang sisa daftar di luar urutan).
Lauren

Jawaban:

6

Saya akan pergi untuk stringdistpaket yang telah mengimplementasikan banyak algoritma untuk menghitung kesamaan parsial (jarak) dari string termasuk Jaro-winkler. Inilah solusi cepat untuk Anda:

  #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

itu output:

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

Anda dapat bermain dengan parameter maxDist dari metode amatch. Meskipun 3 berfungsi terbaik dengan data sampel Anda!

Farid Cheraghi
sumber
Ya, ini berhasil untuk contoh saya! Sekarang untuk menguji beberapa lagi! Pertanyaan terkait: bagaimana saya bisa mencapai gabungan yang sama ini sambil menjaga spasial shapefile? Akan terlihat bahwa sedikit kode ini baru saja membuat bingkai data dengan data yang digabungkan, tetapi saya masih harus dapat memetakannya.
Lauren
Saya telah membuat bingkai data secara manual sehingga masalah Anda dapat direproduksi. Ketika Anda membaca shapefile melalui readOGR, kelas output akan menjadi salah satu dari kelas turunan "sp" seperti "SpatialPointsDataFrame". Dan mereka semua memiliki atribut "data" yang berisi semua data atribut yang bertipe dataframe. Dalam contoh saya, saya bergabung ke kerangka data dan informasi geometris tidak tersentuh. Jadi misalnya Anda, cukup mengubah arm.datake arm@datadan itu akan bekerja dengan baik.
Farid Cheraghi
Jangan gunakan arm@data, itu akan membuat kekacauan besar (catatan tidak cocok dengan geometri yang benar)
Robert Hijmans
6

Saya ingin menambahkan beberapa detail pada jawaban Farid Cher karena ini adalah masalah yang sangat umum. Menggunakan amatchdapat melakukan keajaiban, tetapi dengan Spatialbenda - benda ini Anda tidak boleh menggunakan base::mergedan tidak mengakses @dataslot. Itu pasti akan menyebabkan kekacauan yang mengerikan ( base::mergemengubah urutan catatan, dan mereka tidak lagi cocok dengan geometri).

Sebagai gantinya, gunakan sp::mergemetode ini, dengan menggunakan SpatialPolygonsDataFrameargumen pertama di merge. Perhatikan juga potensi masalah memiliki duplikat catatan. Dan saya menambahkan data sehingga contohnya mandiri dan dapat direproduksi.

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

Ini

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

gagal dengan pesan

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

Karena ada dua catatan untuk "Aragatsotn" di df. Anda bisa melakukannya

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

Tapi biasanya pendekatan waras adalah menggunakan sesuatu seperti

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

Sekarang, penggabungan tidak berfungsi dengan baik dalam hal ini karena nama tidak cocok. Jadi bisa digunakan

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>

Hampir di sana, tetapi "Yerevan City" tidak cocok dengan "Erevan". Dalam hal ini Anda dapat meningkatmaxDist

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

Tetapi meningkatkan maxDisttidak akan selalu berhasil atau memberikan kecocokan yang salah karena nama varian bisa sangat berbeda. Jadi dalam banyak kasus Anda akhirnya akan melakukan beberapa penggantian manual seperti:

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

Dalam kedua kasus diikuti oleh

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

Dalam hal apa pun Anda akan ingin memeriksa apakah sum(table(i) > 1) == 0; Meskipun mergeharus gagal pula jika ada pertandingan duplikat

Robert Hijmans
sumber
Detail yang bagus! Inilah mengapa saya memanggil jawaban saya dengan cepat . Namun kerangka data yang cocok (df) tidak akan berisi data geometri. bukan? OP ingin memetakan df yang bergabung. Agregat spasial alih-alih agregat atribut akan menjadi alternatif lain untuk beberapa kasus gabungan.
Farid Cheraghi
df tidak memiliki geometri, karenanya langkah terakhir dengan merge. Agregat spasial berguna untuk kasus yang berbeda (jika, dalam contoh ini, NAME_1memiliki duplikat.)
Robert Hijmans