Pembukaan
Geospatial adalah salah satu hal yang penting dalam pengolahan data. Dengan geospatial, kita bisa memberikan informasi yang hanya dengan bantuan peta maka informasi tersebut dapat tersampaikan dengan baik.
Ada banyak jenis dari geospatial yang dapat kita lakukan, seperti Dot Map, Connection Map, Choropleth, Map Hexbin, dan Bubble Map1. Tiap jenis dari geospatial memiliki fungsinya masing-masing, dan tiap jenis ini juga membutuhkan jenis dari informasi/data yang berbeda-beda.
Kali ini, kita akan mempelajari mengenai salah satu bentuk dari geocoding yaitu choropleth. Kejaran kita dari materi ini:
- Mengambil Data
- Menggunakan Shapefile
- Jenis-jenis data yang digunakan
- Pengenalan mapshaper
- Memilih data yang ingin difokuskan
- Membaca data
.json
- Menggunakan
.rds
- Menggunakan Shapefile
- Mengolah Data
- Data Hidran
- Penggabungan Data
- Menyajikan Data dengan
leaflet
Mengambil Data
Pertama-tama, kita akan mengunduh terlebih dahulu data dari GADM. Kita akan fokus pada negara Indonesia. Dalam pengambilan data, ada dua cara yang bisa kita lakukan, yaitu pengolahannya terlebih dahulu menggunakan data Shapefile, atau langsung diolah menggunakan R dengan data .rds. Kita dapat pula membaca keseluruhan data menggunakan fungsi readOGR dari package rgdal, tapi untuk topik ini tidak akan kita bahas kali ini.
Data Shapefile
Data shapefile adalah kumpulan data yang digunakan pada banyak aplikasi geospatial. Keuntungan dari data Shapefile adalah selain dapat diakses di banyak aplikasi, data Shapefile juga dapat dilihat dan diolah sedikit dalam mapshaper (kita akan membahasnya setelah ini) untuk dipastikan informasi di dalamnya sebelum diekstrak ke ekstensi file yang kita butuhkan.
Pada saat kita melihat data Shapefile (.shp) yang sudah kita download dari GADM, kita bisa melihat bahwa tiap bagian terbagi ke beberapa file sekaligus2:
.shp: data utama yang berisi informasi geometri (utama).shx: file indeks dari tiap geometri (utama).dbf: data yang berisi atribut-atribut pada tiap area di data (utama).prj: file yang menyimpan informasi sistem koordinat (opsional, digunakan di ArcGIS).cpg: file yang digunakan untuk menentukan code page untuk mengidentifikasi set karakter yang digunakan (opsional)
Karena informasi yang kita nanti akan gunakan tersebar ke beberapa file sekaligus dengan ekstensi yang berbeda-beda, kita akan mengubahnya terlebih dahulu ke satu file agar lebih mudah diakses/diolah di R. Untuk itu, kita akan mengolahnya dulu dalam mapshaper. Setelah diolah dan kita bersihkan tergantung dari daerah yang kita butuhkan, kita bisa mengekstrak datanya dalam bentuk .json saja.
“Tampilan pertama mapshaper”
Mapshaper akan menerima beberapa data dan mengumpulkannya sekaligus. Dari 5 ekstensi yang kita dapatkan dari tiap shapefile, kita dapat memasukkan semuanya kecuali .cpg. Kita masukkan semuanya, kita klik bagian detect line intersection untuk memastikan apakah data yang kita gunakan terdapat titik pertemuan (intersection/point of contact) atau tidak, lalu kita import.
“Impor data yang ingin digunakan”
Dari hasil yang sudah kita impor, kita bisa melihat bahwa data gadm36_IDN_3 ini merupakan data yang memotong hingga kecamatan. Di data ini juga kebetulan peta yang kita dapatkan merupakan peta yang ‘bagus’, dikarenakan tidak terdapat titik pertemuan/intersection3.
“Hasil impor data”
“Contoh data yang terdapat titik pertemuan”
“Contoh titik pertemuan”
Setelah itu, kita akan mengaktifkan informasi mengenai tiap area yang ingin kita lihat, dengan memencet tombol i di sebelah kanan atas. Kita bisa lihat bahwa terdapat informasi mengenai negara (dalam variabel NAME_0), provinsi (NAME_1), kabupaten/kota (NAME_2), kecamatan (NAME_3), juga kelurahan/desa (NAME_4) dari tiap tempat. Selain itu juga, terdapat ID dari tiap bagian, namun tidak akan kita gunakan di sini.
Kita bisa langsung mengekspornya dalam bentuk TopoJSON (serupa namun lebih enteng dibandingkan GeoJSON, dan juga dikarenakan data kita sudah cukup bersih4). Namun untuk pembelajaran di sini, kita akan mencoba untuk terlebih dahulu memilih/mem-filter daerah yang ingin kita fokuskan.
Pada mapshaper, terdapat console yang bisa kita gunakan untuk sedikit mengolah data peta yang sebelumnya kita baca. Kita dapat melihat perintah yang bisa kita gunakan dengan help dan melihat contoh penggunaan console dengan tips.
Kita akan coba memilih hanya dalam cangkupan Kecamatan (gadm36_IDN_3) dan kita filter hanya pada daerah DKI Jakarta:
filter 'NAME_1 == "Jakarta Raya"'
“Hasil filter data”
Setelah itu, kita akan mengekspor datanya sebagai TopoJSON dengan mengeklik Export di bagian kanan atas.
Lalu, untuk membaca data .json di R, kita akan menggunakan package geojsonio. Pada fungsi geojson_read, kita menentukan what = "sp" untuk memastikan data yang kita baca merupakan kelas spasial, bukan list. Dikarenakan data yng kita miliki dari sumber yang berbeda dan ada perbedaan pada bagian “Kepulauan Seribu” (vs “Kep. Seribu”), kita akan menghilangkan bagian tersebut.
Bila ingin mengecek datanya terlebih dahulu, kita dapat fokus pada isi tabelnya dengan menggunakan @
## id GID_0 NAME_0 GID_1 NAME_1 NL_NAME_1 GID_2
## 0 <NA> IDN Indonesia IDN.7_1 Jakarta Raya IDN.7.1_1
## 1 <NA> IDN Indonesia IDN.7_1 Jakarta Raya IDN.7.1_1
## 2 <NA> IDN Indonesia IDN.7_1 Jakarta Raya IDN.7.1_1
## 3 <NA> IDN Indonesia IDN.7_1 Jakarta Raya IDN.7.1_1
## 4 <NA> IDN Indonesia IDN.7_1 Jakarta Raya IDN.7.1_1
## 5 <NA> IDN Indonesia IDN.7_1 Jakarta Raya IDN.7.1_1
## NAME_2 NL_NAME_2 GID_3 NAME_3 VARNAME_3 NL_NAME_3
## 0 Jakarta Barat IDN.7.1.1_1 Cengkareng
## 1 Jakarta Barat IDN.7.1.2_1 Grogolpetamburan
## 2 Jakarta Barat IDN.7.1.3_1 Kalideres
## 3 Jakarta Barat IDN.7.1.4_1 Kebonjeruk
## 4 Jakarta Barat IDN.7.1.5_1 Kembangan
## 5 Jakarta Barat IDN.7.1.6_1 Palmerah
## TYPE_3 ENGTYPE_3 CC_3 HASC_3
## 0 Kecamatan Sub-district 3174070
## 1 Kecamatan Sub-district 3174040
## 2 Kecamatan Sub-district 3174080
## 3 Kecamatan Sub-district 3174020
## 4 Kecamatan Sub-district 3174010
## 5 Kecamatan Sub-district 3174030
# Mengubah menjadi bentuk sf
jakarta_json_mod <- sf::st_as_sf(jakarta_json)
# Menghilangkan daerah Kepulauan Seribu dan menghilangkan kolom-kolom yang tidak dibutuhkan
jakarta_json_mod <- jakarta_json_mod %>%
# Agar mudah digabung
mutate(NAME_3 = str_replace_all(NAME_3, fixed(" "), "") %>% str_to_title()) %>%
# Kepulauan Seribu Dihilangkan
filter(NAME_2 != "Kepulauan Seribu") %>%
# Menghilangkan beberapa kolom
dplyr::select(-c(id, NL_NAME_1, NL_NAME_2, NL_NAME_3, VARNAME_3, HASC_3, TYPE_3, ENGTYPE_3))
glimpse(jakarta_json_mod)## Observations: 45
## Variables: 10
## $ GID_0 <fct> IDN, IDN, IDN, IDN, IDN, IDN, IDN, IDN, IDN, IDN, IDN...
## $ NAME_0 <fct> Indonesia, Indonesia, Indonesia, Indonesia, Indonesia...
## $ GID_1 <fct> IDN.7_1, IDN.7_1, IDN.7_1, IDN.7_1, IDN.7_1, IDN.7_1,...
## $ NAME_1 <fct> Jakarta Raya, Jakarta Raya, Jakarta Raya, Jakarta Ray...
## $ GID_2 <fct> IDN.7.1_1, IDN.7.1_1, IDN.7.1_1, IDN.7.1_1, IDN.7.1_1...
## $ NAME_2 <fct> Jakarta Barat, Jakarta Barat, Jakarta Barat, Jakarta ...
## $ GID_3 <fct> IDN.7.1.1_1, IDN.7.1.2_1, IDN.7.1.3_1, IDN.7.1.4_1, I...
## $ NAME_3 <chr> "Cengkareng", "Grogolpetamburan", "Kalideres", "Kebon...
## $ CC_3 <fct> 3174070, 3174040, 3174080, 3174020, 3174010, 3174030,...
## $ geometry <MULTIPOLYGON> MULTIPOLYGON (((106.7004 -6..., MULTIPOLYGON...
Data .rds
Untuk data .rds, kita dapat langsung memilih R (sf) di GADM5. Setelah itu, kita dapat langsung membacanya.
Lalu karena kita ingin fokus pada Jakarta Raya, kita akan mengolah datanya terlebih dahulu
jakarta_rds <- indonesia_rds %>%
mutate(NAME_3 = str_replace_all(NAME_3, fixed(" "), "") %>% str_to_title()) %>%
filter(NAME_1 == "Jakarta Raya", NAME_2 != "Kepulauan Seribu") %>%
dplyr::select(-c(NL_NAME_1, NL_NAME_2, NL_NAME_3, VARNAME_3, HASC_3, TYPE_3, ENGTYPE_3))
glimpse(jakarta_rds)## Observations: 45
## Variables: 10
## $ GID_0 <chr> "IDN", "IDN", "IDN", "IDN", "IDN", "IDN", "IDN", "IDN...
## $ NAME_0 <chr> "Indonesia", "Indonesia", "Indonesia", "Indonesia", "...
## $ GID_1 <chr> "IDN.7_1", "IDN.7_1", "IDN.7_1", "IDN.7_1", "IDN.7_1"...
## $ NAME_1 <chr> "Jakarta Raya", "Jakarta Raya", "Jakarta Raya", "Jaka...
## $ GID_2 <chr> "IDN.7.1_1", "IDN.7.1_1", "IDN.7.1_1", "IDN.7.1_1", "...
## $ NAME_2 <chr> "Jakarta Barat", "Jakarta Barat", "Jakarta Barat", "J...
## $ GID_3 <chr> "IDN.7.1.1_1", "IDN.7.1.2_1", "IDN.7.1.3_1", "IDN.7.1...
## $ NAME_3 <chr> "Cengkareng", "Grogolpetamburan", "Kalideres", "Kebon...
## $ CC_3 <chr> "3174070", "3174040", "3174080", "3174020", "3174010"...
## $ geometry <MULTIPOLYGON [°]> MULTIPOLYGON (((106.7004 -6..., MULTIPOL...
Bisa kita lihat bahwa kedua data sudah berisi informasi yang sama dan dengan kelas yang juga sama. Perlu diketahui bahwa pada saat nanti kita menggunakan leaflet, obyek data yang dapat dibaca salah satunya adalah data frame spasial dari package lf (lihat ?leaflet untuk keterangan lebih lanjut).
## [1] "sf" "data.frame"
## [1] "sf" "data.frame"
Kita dapat melanjutkan menggunakan salah satu dari data yang sebelumnya sudah kita baca. Untuk pembelajaran, akan dilanjutkan menggunakan data yang dibaca melalui mapshaper (jakarta_json_mod).
Mengolah Data
Dalam tahapan pengolahan data, kita akan mencoba untuk menyocokkan isi dari data hidran yang nanti akan kita tampilkan informasinya di peta, dengan data geometri yang sebelumnya sudah kita baca (dan kita olah sedikit). Hal ini bermaksud agar tidak terjadinya bentrok dikarenakan nama daerah yang berbeda, dan agar lebih masuk akal dilakukan.
Data Hidran
Kita akan membaca data hidran yang berisi informasi mengenai hidran-hidran yang tersebar di daerah Jakarta Raya bersama dengan kondisi(Baik/Rusak/Hilang) dan lokasi daerah tersebut (bukan lokasi hidrannya).
hidran <- read.csv("data_input/clean/hidran.csv")
hidran_mod <- hidran %>%
mutate(lokasi = str_replace_all(lokasi, fixed(" "), "") %>% str_to_title()) %>%
filter(wilayah != "Kepulauan Seribu")
head(hidran_mod)## no wilayah lokasi kondisi jumlah latitude longitude
## 1 1 Jakarta Pusat Gambir Baik 20 -6.169115 106.8165
## 2 2 Jakarta Pusat Tanahabang Baik 36 -6.206441 106.8122
## 3 3 Jakarta Pusat Menteng Baik 30 -6.195425 106.8346
## 4 4 Jakarta Pusat Joharbaru Baik 15 -6.185153 106.8575
## 5 5 Jakarta Pusat Cempakaputih Baik 21 -6.180330 106.8687
## 6 6 Jakarta Pusat Kemayoran Baik 34 -6.160978 106.8533
Ada beberapa modifikasi lain yang akan dilakukan:
- Kondisi “Hilang” akan diganti dengan “Rusak” dikarenakan factor “Hilang” memberi sangat sedikit atau tidak sama sekali kontribusi terhadap data
- Kondisi akan dijumlahkan berdasarkan wilayah dan lokasi agar level dari “Rusak” asli dengan “Rusak” hasil perubahan dari “Hilang” dapat tergabung menjadi satu
hidran_mod <- hidran_mod %>%
mutate(kondisi = ifelse(kondisi == "Hilang", "Rusak", paste(kondisi))) %>%
group_by(wilayah, lokasi, kondisi) %>%
summarise(jumlah = sum(jumlah)) %>%
ungroup()
glimpse(hidran_mod)## Observations: 82
## Variables: 4
## $ wilayah <fct> Jakarta Barat, Jakarta Barat, Jakarta Barat, Jakarta B...
## $ lokasi <chr> "Cengkareng", "Cengkareng", "Grogolpetamburan", "Grogo...
## $ kondisi <chr> "Baik", "Rusak", "Baik", "Rusak", "Baik", "Rusak", "Ba...
## $ jumlah <int> 13, 1, 21, 4, 3, 3, 5, 2, 36, 7, 7, 7, 7, 7, 21, 10, 2...
Penggabungan Data
Dalam penggabungan data, ada beberapa hal yang dilakukan:
- Menggabungkan data
jsonyang kita miliki dengan datahidran, di manaNAME_2pada datajsonakan disandingkan denganwilayahpada datahidran, danNAME_3denganlokasi - Melakukan fungsi
completeuntuk mengisikondisi(Baik/Rusak) pada daerah yang tidak tertera sebelumnya di datahidran - Dilakukan pengurutan untuk mengisi nilai-nilai
NApada data - Menggunakan fungsi
filldaritidyruntuk mengisi nilai-nilai yang hilang kecuali kolomjumlahdankondisi, dengan arah menurun (down). Hal ini dilakukan karena ada kondisi saat kita memiliki data geometrinya namun tidak memilikijumlahpada tiapkondisi, ada pula yang sebaliknya. Dengan melakukan ini, kita akan mendapatkan informasi pada tiap kolom dengan komplit kecualijumlah,geometry, dankondisisaja - Mengisi nilai pada kolom
geometrydengan looping. Di sini digunakan fungsilengthsyang mengembalikan apakah suatu list memiliki isi atau tidak. Bila kosong, akan diisi dengan list di atasnya (menggunakan fungsilist.takedari libraryrlistdan diambil list ke[i-1]atau sebelumnya sebanyak 1) - Mengganti nilai
NApadajumlahdengan0 - Menghilangkan baris-baris dengan
kondisi == NA
jakarta_hidran <- jakarta_json_mod %>%
left_join(hidran_mod, by = c("NAME_2" = "wilayah", "NAME_3" = "lokasi")) %>%
complete(kondisi, NAME_3) %>%
arrange(NAME_3, NAME_0) %>%
tidyr::fill(-c(jumlah, kondisi), .direction = "down")
# Looping untuk mengisi nilai geometri yang kosong pada baris
for (i in 1:nrow(jakarta_hidran)) {
jakarta_hidran$geometry[i] <- ifelse(!lengths(jakarta_hidran$geometry[i]),
rlist::list.take(jakarta_hidran$geometry[i-1], n = 1),
jakarta_hidran$geometry[i])
}
jakarta_hidran <- jakarta_hidran %>%
mutate(jumlah = replace_na(jumlah, replace = 0)) %>%
na.omit()
glimpse(jakarta_hidran)## Observations: 90
## Variables: 12
## $ kondisi <chr> "Baik", "Rusak", "Baik", "Rusak", "Baik", "Rusak", "B...
## $ NAME_3 <chr> "Cakung", "Cakung", "Cempakaputih", "Cempakaputih", "...
## $ GID_0 <fct> IDN, IDN, IDN, IDN, IDN, IDN, IDN, IDN, IDN, IDN, IDN...
## $ NAME_0 <fct> Indonesia, Indonesia, Indonesia, Indonesia, Indonesia...
## $ GID_1 <fct> IDN.7_1, IDN.7_1, IDN.7_1, IDN.7_1, IDN.7_1, IDN.7_1,...
## $ NAME_1 <fct> Jakarta Raya, Jakarta Raya, Jakarta Raya, Jakarta Ray...
## $ GID_2 <fct> IDN.7.4_1, IDN.7.4_1, IDN.7.2_1, IDN.7.2_1, IDN.7.1_1...
## $ NAME_2 <fct> Jakarta Timur, Jakarta Timur, Jakarta Pusat, Jakarta ...
## $ GID_3 <fct> IDN.7.4.1_1, IDN.7.4.1_1, IDN.7.2.1_1, IDN.7.2.1_1, I...
## $ CC_3 <fct> 3172080, 3172080, 3173050, 3173050, 3174070, 3174070,...
## $ jumlah <dbl> 51, 12, 21, 10, 13, 1, 10, 16, 57, 9, 0, 0, 16, 1, 0,...
## $ geometry <MULTIPOLYGON> MULTIPOLYGON (((106.9508 -6..., MULTIPOLYGON...
Pada saat kita melakukan pemetaan leaflet sederhana menggunakan data jakarta_hidran, akan didapatkan error seperti ini:
## Error in polygonData.default(data): Don't know how to get path data from object of class tbl_df
Hal ini dikarenakan pada saat kita melakukan fungsi complete, data kita akan diubah ke dalam bentuk tbl_df, dan ini membuat kita tidak dapat melakukan pemetaan.
## [1] "tbl_df" "tbl" "data.frame"
Untuk itu, kita akan ubah lagi data kita menjadi data sf.
## [1] "sf" "tbl_df" "tbl" "data.frame"
Menyajikan Data dengan leaflet
Dalam penyajian data, pertama-tama kita akan memasukkan data kita dalam leaflet. Lalu, akan ditentukan pewarnaan yang sesuai untuk choropleth yang nanti akan dibentuk. Palette ditentukan dari merah hingga biru (silahkan lihat cheatsheet yang sudah disediakan). Selain itu, dibuat juga popup pada tiap tempat.
# leaflet
m <- leaflet(jakarta_hidran_sf)
# Pewarnaan
temp_baik <- jakarta_hidran_sf %>%
filter(kondisi == "Baik") %>%
dplyr::select(GID_3, NAME_3, jumlah)
temp_rusak <- jakarta_hidran_sf %>%
filter(kondisi == "Rusak") %>%
dplyr::select(GID_3, NAME_3, jumlah)
# -1 <= x <= 1
col_formula <- (temp_baik$jumlah - temp_rusak$jumlah)/(temp_baik$jumlah + temp_rusak$jumlah)
col <- col_formula %>% replace(is.nan(.), 0)
bins <- c(-1, fivenum(col))
pal <- colorBin("RdYlBu", domain = col, bins = bins)
# Popup
popup.cont <- paste("<h2><b>", jakarta_hidran_sf$NAME_3, "</b></h2>",
"<h4><b> Baik: ", temp_baik$jumlah, "</h4></b>",
"<h4><b> Rusak: ", temp_rusak$jumlah, "</h4></b>"
)Dan yang terakhir, kita akan membentuk peta dengan tiles yang disediakan oleh CartoDB menggunakan fungsi addProviderTiles. Lalu kita atur poligon dan legenda pada peta. Kita juga ubah nama tiap elemen pada legenda agar lebih masuk akal.
m %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
addPolygons(fillColor = ~pal(col),
weight = 1,
opacity = 1,
color = "black",
dashArray = "3",
fillOpacity = 0.5,
label = paste0("Kecamatan: ", jakarta_hidran_sf$NAME_3),
popup = popup.cont) %>%
addLegend("bottomright",
pal = pal,
values = ~col,
title = "Hydrant Condition",
labFormat = labelFormat(digits = 2),
opacity = 1)Sebagai catatan, kita juga bisa mengatur tiap bagian pada legend dengan mengatur warnanya satu-persatu (diambil dengan melakuakn inspect element) dan dapat kita beri label. Kalau tidak dilakukan seperti ini, maka teks pada legenda tidak dapat berubah dan tetap ditunjukkan dalam bentuk numerik walaupun sudah diberi parameter labels. Perlu diketahui juga agar parameter colors bisa berjalan, parameter pal pada addLegend harus dihilangkan.
m %>%
addProviderTiles(providers$CartoDB.DarkMatter) %>%
addPolygons(fillColor = ~pal(col),
weight = 1,
opacity = 1,
color = "black",
dashArray = "3",
fillOpacity = 0.5,
label = paste0("Kecamatan: ", jakarta_hidran_sf$NAME_3),
popup = popup.cont) %>%
addLegend("bottomright",
values = ~col,
colors =c("#D7191C", "#FDAE61", "#FFFFBF", "#ABD9E9", "#2C7BB6"), # Ambil dari inspect element dengan kondisi parameter `pal` benar
labels= c("Terrible", "Bad", "Decent/No Hydrant", "Good", "Great"),
title = "Hydrant Condition",
labFormat = labelFormat(digits = 2),
opacity = 1)Informasi Tambahan
https://www.data-to-viz.com/ - contoh-contoh penggunaan peta untuk data↩
http://webhelp.esri.com/arcgisdesktop/9.3/index.cfm?TopicName=Shapefile_file_extensions - Informasi mengenai ekstensi dari shapefile↩
Bila data yang kita baca memiliki titik pertemuan, kita dapat menyimpulkannya terlebih dahulu sebelum kita ekstrak untuk menghindari hal-hal yang tidak diinginkan. Hal ini dapat dilakukan dengan
simplifydi bagian kanan atas. Untuk melihat informasi dari tiap poin, silahkan dicek di bagian?.↩https://stackoverflow.com/questions/14740705/difference-between-geojson-and-topojson - Perbedaan antara GeoJSON dengan TopoJSON↩
http://strimas.com/r/tidy-sf/ -
sfmenggantikanspuntuk menangani obyek spasial, dan dibentuk untuk melengkapitidyverse↩