R’da Web Kazıma, Veri Manipülasyonu, Kümeleme ve Görselleştirme

İllerin genel seçimlerde verdikleri oylar üzerinden ortalama ve standart sapmayı kullanarak bir çalışma yapmıştım. Bu yazıda o çalışmanın detaylarını vereceğim.

Verileri Vikipedi’den elde ettim. Web kazıma (web scraping) ile bu verileri rahat bir şekilde alabiliyoruz. Web kazıma yapmadan önce temel bir HTML bilgisine sahip olmanızı öneririm fakat bu uygulamada buna pek ihtiyaç olmayacak. Daha ileri seviye uygulamalarda bu bilgi önem kazanacaktır.

Linklere şuradan ulaşabilirsiniz: 2002, 2007, 2011, 2015-Haziran, 2015-Kasım, 2018

Çalışmamızı genel seçimler üzerinden yapacağımız için sadece 6 genel seçime ait linkleri verdim.

R’da web kazıma yapmanıza imkan veren paketlerden birisi rvest’tir. Paketi yükleyip çağırdıktan sonra şunları kullanacağız:

#install.packages("rvest")
library(rvest)
  • read_html(): HTML dosyasına ait içeriği okumak için kullanılır.
  • html_table(): HTML’deki tabloları çekmeyi sağlar. Eğer fill = TRUE eklenirse eksik verileri NA ile doldurur. Şurada Hadley Wickham’ın örneği parametrenin ne işe yaradığını çok basit bir şekilde anlatıyor.

Bunun yanında çektiğimiz veri tam istediğimiz gibi gelmeyeceği için veri manipülasyonu da yapmamız gerekiyor. Veri manipülasyonu verileri daha düzenli bir hale getirir ve verilerin okunmasını kolaylaştırır. Bunun için veri manipülasyonunun dilbilgisi olan dplyr paketini kullanacağız. Veri görselleştirmenin dilbilgisi ise ggplot2 paketidir. Görselleştirme ile ilgili ayrıca detaylı Türkçe kaynağa buradan ulaşabilirsiniz. Bahsettiğim üç paketi yükleyip çağıralım; ekstra paketlerden yazının devamında bahsedeceğim.

#install.packages("dplyr")
#install.packages("ggplot2")

library(dplyr)
library(ggplot2)

Bir link üzerinden web kazıma nasıl yapılır bakalım.

link <- "https://tr.wikipedia.org/wiki/2018_T%C3%BCrkiye_genel_se%C3%A7imleri"
df2018 <- read_html(link) %>% 
  html_table(fill = TRUE) %>% 
  .[[21]]

İşte bu kadar.

.[[21]] yazmasaydık bu link için 23 tane tablo getirecekti. Bu tablolardan ihtiyacımız olanı listeye tıklayıp bulabiliriz.

Bu işlemleri 6 link için tek tek yapabiliriz. Bu bir yoldur. Diğer bir yol ise bunları bir döngü yardımı ile almak olabilir. Eğer formatlar farklı olmasaydı bunu rahatlıkla yapabilirdik. Fakat şu problemler ile karşılaşacağız:

  • Vikipedi sayfalarında tabloların hepsi aynı sırada bulunmayabilir. Örneğin, 2018 yılı için .[[21]] dedik; bu 2002 için .[[7]] olacaktır.
  • Vikipedi sayfalarında tabloların sütun yerleri uyuşmayabilir. Örneğin, oylarını alacağımız iki partinin sütun numarası 2018’de 5 ve 8 iken; 2002’de 4 ve 6 olacaktır.

Şimdi bir for döngüsü yazalım ve problemleri tek tek ortadan kaldıralım.


link <- c(
  "https://tr.wikipedia.org/wiki/2002_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/2007_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/2011_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/Haziran_2015_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/Kas%C4%B1m_2015_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/2018_T%C3%BCrkiye_genel_se%C3%A7imleri"
)

election <- c("2002","2007","2011","2015-1","2015-2","2018")
tblno <- c(7,7,9,22,19,21)
colno4akp <- c(4,4,4,4,5,5)
colno4chp <- c(6,6,6,6,8,8)

df <- data.frame()

for(i in 1:6){
  
  tbl <- read_html(link[i]) %>% 
    html_table(fill = TRUE) %>% 
    .[[tblno[i]]] %>% 
    `colnames<-`(make.unique(names(.))) %>% 
    select(1,colno4akp[i],colno4chp[i]) %>% 
    rename("province"=1,"vote_rate_akp"=2,"vote_rate_chp"=3) %>% 
    mutate(election = election[i]) %>% 
    slice(-nrow(.))
  
  df <- df %>% bind_rows(tbl)
  
  if(i == 6){
    
    df <- df %>% 
      mutate(vote_rate_akp = as.numeric(gsub(",",".",vote_rate_akp)),
             vote_rate_chp = as.numeric(gsub(",",".",vote_rate_chp)),
             province = ifelse(province == "Afyon", "Afyonkarahisar",province),
             province = gsub("\\(toplam)","", province)) %>% 
      filter(!(grepl("\\(I)|\\(II)|\\(III)",province))) %>% 
      mutate(province = gsub(" ","", province))
    
  }
  
}

Neler yaptık adım adım gidelim.

Önce kazıyacağımız linkleri vektör haline getirdik. for döngüsünde bunların içinde dolaşıp bunları tek tek alacağız.

link <- c(
  "https://tr.wikipedia.org/wiki/2002_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/2007_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/2011_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/Haziran_2015_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/Kas%C4%B1m_2015_T%C3%BCrkiye_genel_se%C3%A7imleri",
  "https://tr.wikipedia.org/wiki/2018_T%C3%BCrkiye_genel_se%C3%A7imleri"
)

election adında bir vektör oluşturduk ve içine seçim dönemlerini attık. for döngüsü çalıştığında her bir veri çerçevesini alt alta birleştireceğiz. Bu nedenle ilgili seçim dönemini atayarak oranların ne zaman alındığını karıştırmayacağız.

election <- c("2002","2007","2011","2015-1","2015-2","2018")

tblno adında bir vektör oluşturduk. Bu, Vikipedi sayfalarında tabloların kaçıncı sırada olduğunu gösteriyor.

tblno <- c(7,7,9,22,19,21)

colno4akp ve colno4chp adında iki vektör oluşturduk. Bunlar ise tablolarda iki partinin kaçıncı sütunda olduğunu gösteriyor.

colno4akp <- c(4,4,4,4,5,5)
colno4chp <- c(6,6,6,6,8,8)

df adında boş bir veri çerçevesi oluşturduk. Döngü her çalıştığında bu veri çerçevesi bir tane tablo ile dolacak; döngü bittiğinde ise tüm tablolar df’in içinde yerini alacak.

df <- data.frame()

Geldik for döngüsüne… En basit hali ile ele alalım.

for(i in 1:6){
  tbl <- read_html(link[i]) %>% 
    html_table(fill = TRUE) %>% 
    .[[tblno[i]]]
}

i değişkeni 1’den 6’ya kadar değerlere eşit olacak ve döngü bitecek. read_html(link[i]) ile link vektöründeki her bir link alınıp işlenecek. html_table(fill = TRUE) yapısına hakimiz zaten. Kaçıncı tablo olduğunu ise tblno vektöründen alacak. O nedenle .[[tblno[i]]] yazdık. Tüm bunları ise tbl objesine kaydedecek.

for(i in 1:6){
  tbl <- read_html(link[i]) %>% 
    html_table(fill = TRUE) %>% 
    .[[tblno[i]]] %>% 
    `colnames<-`(make.unique(names(.))) %>% 
    select(1,colno4akp[i],colno4chp[i]) %>% 
    rename("province"=1,"vote_rate_akp"=2,"vote_rate_chp"=3) %>% 
    mutate(election = election[i]) %>% 
    slice(-nrow(.))
}

Döngünün içeriğini biraz daha genişlettik. `colnames<-`(make.unique(names(.))) eklememin sebebi aldığım hata. Sütun isimleri eşit olduğu zaman R hata veriyor. Bu satır isimlerin tekil olmasını sağlayacak. select(1,colno4akp[i],colno4chp[i]) ihtiyacımız olan sütunları seçecek. Birinci sütun illerin ismi olduğu için o sabit kalacak; diğerleri daha önce oluşturduğumuz colno4akp ve colno4chp vektörlerinden bu bilgiyi alacak. rename("province" = 1,"vote_rate_akp" = 2,"vote_rate_chp" = 3) seçilecek sütunların ismini değiştirecek. 1, 2 ve 3 ile kaçıncı sütunların değişeceğine karar veriyoruz. mutate(election = election[i]) seçim dönemlerinin ismini election vektöründen alıp bu bilgileri yeni bir sütuna koyacak; ismi ise election olacak. Her bir tablonun sonunda Türkiye toplamı olduğu için bunları slice(-nrow(.)) ile kaldıracağız. Bu arada R’da pipe operatörü (%>%) ile işlem yaparken bir fonksiyonda veri çerçevesinin ismini yazmak yerine nokta koyabilirsiniz.

for(i in 1:6){
  tbl <- read_html(link[i]) %>% 
    html_table(fill = TRUE) %>% 
    .[[tblno[i]]] %>% 
    `colnames<-`(make.unique(names(.))) %>% 
    select(1,colno4akp[i],colno4chp[i]) %>% 
    rename("province"=1,"vote_rate_akp"=2,"vote_rate_chp"=3) %>% 
    mutate(election = election[i]) %>% 
    slice(-nrow(.))
  
  df <- df %>% bind_rows(tbl)
}

Döngünün içeriğini biraz daha genişlettik. Yapılan işlemler sonucunda bir tbl objesinin yaratılacağından bahsetmiştik. Ama bu tbl objesini daha önce oluşturduğumuz df ile birleştirmemiz gerekiyor. df <- df %>% bind_rows(tbl) ile döngü her çalıştıkça df ve tbl birleşmesi sağlanmış olacak. Böylece her döngüde df daha da büyümüş olacak.

Geldik son olan 6. link çalıştıktan sonra df veri çerçevesine son bir şekil vermeye…

for(i in 1:6){
  
  tbl <- read_html(link[i]) %>% 
    html_table(fill = TRUE) %>% 
    .[[tblno[i]]] %>% 
    `colnames<-`(make.unique(names(.))) %>% 
    select(1,colno4akp[i],colno4chp[i]) %>% 
    rename("province"=1,"vote_rate_akp"=2,"vote_rate_chp"=3) %>% 
    mutate(election = election[i]) %>% 
    slice(-nrow(.))
  
  df <- df %>% bind_rows(tbl)
  
  if(i == 6){
    
    df <- df %>% 
      mutate(vote_rate_akp = as.numeric(gsub(",",".",vote_rate_akp)),
             vote_rate_chp = as.numeric(gsub(",",".",vote_rate_chp)),
             province = ifelse(province == "Afyon", "Afyonkarahisar", province),
             province = gsub("\\(toplam)","", province)) %>% 
      filter(!(grepl("\\(I)|\\(II)|\\(III)",province))) %>% 
      mutate(province = gsub(" ","", province))
    
  }
  
}

Eğer i değişkeni 6’ya eşitse, if(i == 6), şunları yap:

vote_rate_akp ve vote_rate_chp sütunlarına ait değerlerdeki virgülü kaldır; nokta yap ve bunu sayı formatına çevir. vote_rate_akp = as.numeric(gsub(",", ".", vote_rate_akp)) ve vote_rate_chp = as.numeric(gsub(",", ".", vote_rate_chp))

province Afyon ise bunu Afyonkarahisar’a çevir; değilse province’ın kendisini yaz. province = ifelse(province == "Afyon", "Afyonkarahisar",province)

Eğer ilin yanında (toplam) yazıyorsa bunu kaldır. province = gsub("\\(toplam)","", province)

Eğer ilin yanında (I), (II) veya (III) varsa o satırı kaldır. filter(!(grepl("\\(I)|\\(II)|\\(III)", province)))

province sütununda herhangi bir boşluk varsa kaldır. mutate(province = gsub(" ","", province))

Bitti. Artık analize hazırız.

Analizin birinci bölümünde her bir ilin ortalama oyunu ve standart sapmasını alacağız ve bunu görsel olarak göstereceğiz. Analizin ikinci bölümünde ise detaylarına fazla girmeden k-means algoritması ile illeri kümelere ayıracağız.

df’in son hali aşağıdaki gibidir.

İşlem yaparken yapı şu olacak:

Bunun için reshape2 paketindeki dcast() fonksiyonundan faydalanabiliriz.

#install.packages("reshape2")
library(reshape2)

profile_akp <- df %>% 
  dcast(., province ~ election, value.var = "vote_rate_akp")

dcast(., province ~ election, value.var = "vote_rate_akp") ile province’ın aşağı doğru; election’ın ise yana doğru olacağını söyledik ve değerlerin de vote_rate_akp olacağını belirttik.

Şimdi bu veri çerçevesine iki tane sütun ekleyeceğiz. Birisi standart sapmayı; diğeri ise ortalamayı alacak. Bunu yapmak için apply() fonksiyonunu kullanacağız.

profile_akp <- df %>% 
  dcast(., province ~ election, value.var = "vote_rate_akp") %>% 
  mutate(StDev = apply(.[(2:ncol(.))],1,sd),
         Mean = apply(.[(2:ncol(.))],1,mean))

StDev sütunundan faydalanarak anlatayım: apply(.[(2:ncol(.))],1,sd) ile önce hangi sütunlarda işlem yapılacağını (2 ile son sütun numarası arası), satırlarda işlem yapılacağını (1) ve son olarak da kullanılacak fonksiyonun standart sapmaya ait olan sd olduğunu belirttik. Mean’de ise mean fonksiyonundan faydalanacak.

4 tane durum olsun:

  • Eğer bir ilin ortalaması tüm illerin ortalamasından büyükse ve standart sapması tüm illerin ortalama standart sapmasından küçükse A grubu,
  • Eğer bir ilin ortalaması tüm illerin ortalamasından büyükse ve standart sapması tüm illerin ortalama standart sapmasından büyükse B grubu,
  • Eğer bir ilin ortalaması tüm illerin ortalamasından küçükse ve standart sapması tüm illerin ortalama standart sapmasından büyükse C grubu,
  • Eğer bir ilin ortalaması tüm illerin ortalamasından küçükse ve standart sapması tüm illerin ortalama standart sapmasından küçükse D grubu olsun.
profile_akp <- df %>% 
  dcast(., province ~ election, value.var = "vote_rate_akp") %>% 
  mutate(StDev = apply(.[(2:ncol(.))],1,sd),
         Mean = apply(.[(2:ncol(.))],1,mean)) %>% 
  mutate(
    Group = ifelse(Mean > mean(Mean) & StDev < mean(StDev), "A",
                   ifelse(Mean > mean(Mean) & StDev > mean(StDev), "B",
                          ifelse(Mean < mean(Mean) & StDev > mean(StDev), "C", "D")))
  )

Koşulları yazdık ve bunu Group adında bir sütuna atadık.

Görselleştirmeyi yapabiliriz.

#install.packages("ggrepel")
library(ggrepel) #il etiketlerinin gösterilmesinde kullanılan bir paket; geom_text_repel()

ggplot(profile_akp, aes(x = StDev, y = Mean, color = Group)) +
  geom_point() +
  geom_text_repel(aes(label = province), size = 3) +
  geom_hline(aes(yintercept = mean(Mean))) +
  geom_vline(aes(xintercept = mean(StDev))) +
  theme_minimal() +
  theme(legend.position = "none") +
  scale_color_manual(values = c("orange","black","blue","red")) +
  labs(title = "İllere Ait AKP Oy Oranları Üzerinden Ortalama-Standart Sapma Bölgesi",
       subtitle = "Genel Seçimler (2002-2018)",
       y = "Ortalama", x = "Standart Sapma",
       caption = "Veriler Vikipedi'den elde edilmiştir.")

Standart sapma ile hedeflediğim nokta bir ilin bir partiye oy verme konusunda ne kadar istikrarlı olduğudur. Standart sapma düştükçe istikrar artıyor; yükseldikçe istikrar bozuluyor. Tabi standart sapma tek başına yeterli olmayacaktır. Bunun yanına bir de ortalama oyu ekledim. Bu ise istikrarın ne tarafta olabileceği konusunda fikir verebilir. Örneğin, 6 seçimde Tunceli ilgili partiye ortalama 12.3% oy vermiş. Standart sapması ise 3.2’dir. Bu da Tunceli’den ilgili partiye istikrarlı bir şekilde oy çıkmıyor şeklinde yorumlanabilir. Bir diğer örnek Konya olsun. Konya, ortalama olarak 6 seçimde 64.7% oy vermiş. Standart sapması ise 6.8’dir. Türkiye ortalamasının altında bir standart sapmadır. İstikrar konusunda bir Tunceli değil ama yine de ciddi bir konumda duruyor. Uç bir örnek Ağrı olabilir. Genel seçimlerde 18.2 standart sapma yapmış en yüksek ilimiz. 33.6% ile Türkiye ortalaması altında bir ortalama oy oranına sahip. Bunun yanında Türkiye ortalaması altında ortalama oya sahip standart sapması yüksek illerin Doğu-Güneydoğu Anadolu’dan olduğu gözlemlenebilir.

Son olarak bu illeri kümelendirelim.

Kümeleme algoritmalarından K-means’i (K-Ortalamalar) seçtim.

install.packages("factoextra")
install.packages("ggforce")
library(factoextra) #uzaklık hesaplamak için kullanılacak; get_dist()
library(ggforce) #küme gruplarını elips ile belirtmek için kullanılacak paket; geom_mark_ellipse()

rownames(profile_akp) <- profile_akp$province #index'e iller atandı
profile_akp$province <- NULL #ilk sütundaki iller çıkartıldı
scaled_akp <- scale(profile_akp[,c(7:8)]) #7 ve 8. sütunların standardize işlemi yapıldı
distance_akp <- get_dist(scaled_akp) #uzaklıklar hesaplandı
k10 <- kmeans(distance_akp, centers = 10, nstart = 25) #10 kümeli bir çıktı oluşturuldu
profile_akp$cluster <- k10$cluster #kümeler illere atandı

ggplot(profile_akp, aes(x = StDev, y = Mean, color = factor(cluster))) +
  geom_point(alpha = 0.5) +
  geom_mark_ellipse(aes(label = cluster),
                    expand = unit(1, "mm"),
                    label.fill = "red",
                    con.linetype = 2,
                    label.fontsize = 7) +
  geom_text_repel(aes(label = row.names(profile_akp)), size = 3, force = 0.1) +
  geom_hline(aes(yintercept = mean(Mean))) +
  geom_vline(aes(xintercept = mean(StDev))) +
  theme_minimal() +
  theme(legend.position = "none") +
  labs(title = "İllere Ait AKP Oy Oranları Üzerinden Ortalama-Standart Sapma Bölgesi",
       subtitle = "Genel Seçimler (2002-2018)",
       y = "Ortalama", x = "Standart Sapma",
       caption = "Veriler Vikipedi'den elde edilmiştir.")

Paylaştığım bir çalışmayı R’da nasıl yapabileceğinizi göstermiş oldum. Umuyorum faydalı bulmuşsunuzdur. İller özelinde veya çalışmanın geneli konusunda analizlerinizi yorum alanına bırakabilirsiniz.

Teşekkürler.

2 thoughts on “R’da Web Kazıma, Veri Manipülasyonu, Kümeleme ve Görselleştirme

  • 10 Mayıs 2021 tarihinde, saat 09:50
    Permalink

    Merak ettiğim bir şey var. Dünyadaki tüm rasathanelerin deprem verileri anlık olarak yayınlanıyor. Bunların datalarını eğitimde kullanılsa, olası depremler tahmin edilebilir mi?

    Yanıtla

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir