R Dersleri - Ali Onur Gitmez

Ana Sayfa Ders 1 Ders 2 Ders 3 Ders 4 Ders 5 Ders 6 Ders 7 Ders 8 Ders 9 Ders 10 Ders 11 Ders 12 Ders 13 Ders 14 Ders 15

String Manipulation

Şu ana kadar yaptığımız analizler genel olarak sayıların dönüşümü üstüne odaklandı. Sayıları nasıl çarpar böler, birbirine ekleyerek yeni değişken oluştururuz konularını öğrenmiştik. Burada ise yine sıkça kullanılan bir veri tipi olan stringler üstünde ne gibi işlemler yapabileceğimizi öğrenecez. Bunu öğrenmek string verileri beklenmedik formatlarda geldiği zaman veya string değişkenlerinin içinden bir kısmı çıkartmak istediğimiz zaman işimize yarayacaktır.

String oluştururken hem “” hem de ’’ yollarını tercih edebiliriz. Ancak bazı durumlarda yaratabileceği farklardan dolayı “” şeklinde yapılması tavsiye edilir.

string1 <- "string"
string2 <- 'string'

Stringleri print etmek için de farklı yöntemler kullanabiliriz. Print objeyi önümüze koyarken cat ise içinde yazan neyse onu verecektir.

print(string1)
## [1] "string"
cat(string2)
## string
paste(string2)
## [1] "string"

Paste ve cat özellikle birden çok stringi birbirine bağlamak istediğimiz zaman avantajlı olur.

paste("Bu hafta R dersinde", string1, "konusunu öğrendik")
## [1] "Bu hafta R dersinde string konusunu öğrendik"
cat("Bu hafta R dersinde", string1, "konusunu öğrendik")
## Bu hafta R dersinde string konusunu öğrendik

Paste aynı zamanda sıralı print etme istediğimiz durumlarda da yardımcı olur.

isimler <- c("Ece", "Onur", "Zeynep")
paste("Merhaba", isimler)
## [1] "Merhaba Ece"    "Merhaba Onur"   "Merhaba Zeynep"

Stringleri arada boşluk olmadan birleştirmek istiyorsak da paste0 kullanabiliriz. Ancak string kendi içinde boşluğa sahipse bu boşluk doğal olarak kalıyor.

paste0("Bugün R dersinde", string1, "konusunu öğrendik")
## [1] "Bugün R dersindestringkonusunu öğrendik"

Eğer istersek belli bir ayraç kullanıp parçaları onunla da ayırabiliriz.

paste("Merhaba", isimler, sep = ",")
## [1] "Merhaba,Ece"    "Merhaba,Onur"   "Merhaba,Zeynep"

Stringlerin belli bir kısmını da almamız mümkündür.

string3 <- "Bu hafta string öğreniyoruz"
ayırma1 <-substr(string3, 1, 4)
ayırma1
## [1] "Bu h"

İlk örnekte 1 ve 4.endeksler arasını aldık. Dikkat etmemiz gereken bir nokta da boşlukların da endekslerden sayıldığı.

ayırma2 <-substring(string3, 4)
ayırma2
## [1] "hafta string öğreniyoruz"

Burdada 4.endeskten sona kadar alıyoruz.

nchar() ile string içinde boşluklar ve noktalama da dahil kaç karakter olduğunu görebiliriz.

nchar(isimler)
## [1] 3 4 6

Stringleri büyük harf ve küçük harf arasında değiştirebiliriz. titlecase her kelimenin ilk harfini büyük yapar.

upper_case <- toupper(ayırma2)
lower_case <- tolower("PATATES")
title_case <- tools::toTitleCase(ayırma2)
upper_case
## [1] "HAFTA STRING ÖĞRENIYORUZ"
lower_case
## [1] "patates"
title_case
## [1] "Hafta String Öğreniyoruz"

Bu tarz işlemler basit, kısa ve hangi harfin nerede oluğunu bildiğimiz durumlarda işe yarar. Ancak gerçek hayatta elimize geçen veriler bu kadar basit olmaz. Üstünde işlem yapmak istediğimiz harfler farklı endekslerde olabilir, öncesinde ve sonrasında başka kelimeler geliyor olabilir. Bu gibi durumlarda her stringe teker teker girmek yerine ortak bir pattern bulup işlem yapmak daha rahat olacaktır. Bunun için R’ın kendi özelliklerini kullanabiliriz.

ulkeler <- c("ABD", "Almanya", "Fransa", "Ingiltere", 
             "Rusya", "Fransa", "Italya")
grep("fransa", ulkeler)
## integer(0)
grep("Fransa", ulkeler)
## [1] 3 6
grepl("Fransa", ulkeler) 
## [1] FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE

Ilk aramalarımızda büyük harf ve küçük harf farketmişti. Her veri böyle depolanmayacağı için bunun önüne geçebiliriz.

grep("fransa", ulkeler, ignore.case = TRUE) 
## [1] 3 6

Belirttiğimiz harfin text içinde geçip geçmeiğini de görebiliriz. Bunların hem pozisyonlarını hem de içerdiği elementleri getirebiliriz.

grep("a", ulkeler, fixed = TRUE)
## [1] 2 3 5 6 7
grep("a", ulkeler, fixed = TRUE, value = TRUE)
## [1] "Almanya" "Fransa"  "Rusya"   "Fransa"  "Italya"

Burada FALSE belirtemimizin sebebi regex diye adlandırdığımız bir sistemden dolayı. Orada özellikle . ve * gibi işaretler stringden ziyade başka anlamlar da ifade ettiği için FALSE diyerek onları string gibi kullanırız. Regexi bu derste göreceğiz zaten.

Patternları bulduğumuz gibi replace de edebiliriz.

sub_replace <- sub("e", "S", isimler, 
                   ignore.case = FALSE, fixed = FALSE)

Bu komut her string içinde ilk karşılayan elementi değiştirir.

sub_replace2 <- gsub("e", "S", isimler, 
                     ignore.case = TRUE, fixed = FALSE)

Bu komutla da karşılayan bütün elementleri değiştirmesini söylüyoruz.

Bu konuları pratik bir örneğe dökecek olursak

string_data <- data.frame(
  isim = c("Ece Ucar", "Zeynep_Onal", 
           "Onur.Gitmez", "ece;ucar")
)
string_data$isim <- gsub("[_;.]", " ", string_data$isim)
string_data$isim <- tools::toTitleCase(string_data$isim)
grepl("Ucar", string_data$isim)
## [1]  TRUE FALSE FALSE  TRUE

Regular Expressions

Şu ana kadar öprendiğimiz konular da aslında regex altına giriyordu. Regex, elimize gelen veri temiz değilse onu temizlemeye yarar. Bununla çalışırken şu ana kadar base R komutlarını öğrendik ve şimdi temel regex komutlarına bakacağız. Ancak diğer işlemlerde olduğu gibi bunun için de özel yazılan R paketleri var. Buna sonradan değineceğiz. Regex ile metakarakter ve özel karakterler kullanarak aramalarımızı gerçekleştirebiliriz. Önceden kullandığımız örnekte fixed false olarak belirtmemizin sebebi de buydu. Bu karakterler özel bir anlama geldiği için regex farklı davranışlar sergileyebilirdi. Burada bu davranışların ne olduğunu kısaca göreceğiz. Normalde çok geniş bir konu olduğu için çok kısaca bahsedeceğim.

Regex daha önceden gördüğümüz bir örnek aslında ama hemen hatırlamak gerekirse, direkt textin karakterleri üstünden regex işlemi yapabiliriz.

text_regex <- "the quick brown fox jumps over 
the lazy dog"
grepl("quick", text_regex) 
## [1] TRUE
grepl("cat", text_regex) 
## [1] FALSE

Metacharacter dedğimiz işaretler ise regex için bir anlam ifade eder. Ondan dolayı bunları kullanırken dikkat etmemiz gerekir. Mesela $ işareti o kelimeyi stringin sonunda aramamız gerekiğini söylediği için dog TRUE sonucu verirken lazy false dedi. Bunu belli stringlerin ne ile bittiğini görmek için kullanabiliriz. Daha bunun gibi dolusuna regex karakteri olduğu için hepsinden bahsetmek mümkün değil.

grepl("dog$", text_regex)
## [1] TRUE
grepl("lazy$", text_regex)
## [1] FALSE

[] arasına almamız ise onun arasına aldığımız karakterleri string içinde aratır.

grepl("[aeiou]", text_regex)
## [1] TRUE

Bütün string işlemlerini ortak bir kod bazına oturtmak için stringr paketini kullanabiliriz. Böylece hem bazı işlemleri daha hızlıca yapabilir hem de daha anlaşılır olmasını sağlayabiliriz.

library(stringr)

Stringin uzunluğunu bulmak için

str_length(text_regex)
## [1] 44

Stringleri birleştirmek için

str_c("Zeynep", "Önal", sep = " ") 
## [1] "Zeynep Önal"

Stringin içinden bir kısmı çıkartmak için

str_sub(text_regex, 3,8)
## [1] "e quic"

Boyut değiştirmek için:

str_to_upper(text_regex)
## [1] "THE QUICK BROWN FOX JUMPS OVER \nTHE LAZY DOG"
str_to_lower(text_regex)
## [1] "the quick brown fox jumps over \nthe lazy dog"
str_to_title(text_regex)
## [1] "The Quick Brown Fox Jumps Over \nThe Lazy Dog"

Pattern üstüne çalışma

str_detect(c("elma", "muz", "ananas"), "a")
## [1]  TRUE FALSE  TRUE
str_subset(c("elma", "muz", "ananas"), "a")
## [1] "elma"   "ananas"

Replace etme. İlki sadece ilk gördüğünü replace ederken, ikinici tamamını replace eder. Ancak burada dikkat edilmesi gereken nokta bunların case sensitive olduğudur.

str_replace(text_regex, "the", "a")
## [1] "a quick brown fox jumps over \nthe lazy dog"
str_replace_all(text_regex, "the", "a")
## [1] "a quick brown fox jumps over \na lazy dog"

Aynı mantığı kelimeleri stirng içinden atmak için de kullanabiliriz.

str_remove(text_regex, "fox")
## [1] "the quick brown  jumps over \nthe lazy dog"
str_remove_all(text_regex, "the")
## [1] " quick brown fox jumps over \n lazy dog"

Stringleri belli bir ayraca göre bölmemiz de mümkün. Burada boşluğu ayırma aracı olarak kullandım ancak virgül ve nokta gibi şeyleri de kullanabiliriz.

str_split(text_regex, " ")
## [[1]]
## [1] "the"   "quick" "brown" "fox"   "jumps" "over"  "\nthe" "lazy"  "dog"

Son olaraksa whitespace dediğimiz bir kavram var. Bu stringlerdeki boşluklar anlamına geliyor. Mesela string başında veya sonunda boşluk bırakırsak orada obje olmamasına rağmen bu varmış gibi görünür. Bundan kurtulmamız string ile daha rahatça çalışmamıza yardımcı olur.

string_deneme <- " R Dersi "
str_trim(string_deneme)
## [1] "R Dersi"

Belli bir patterna uyan stringleri cikarmak icin

str_extract("Elimizdeki sayilardan bazilari 
            385, 672. Bunlari cikarabiliriz.", 
            "\\d+")
## [1] "385"
str_extract_all("Elimizdeki sayilardan bazilari 
                385, 672. Bunlari cikarabiliriz.", 
                "\\d+")
## [[1]]
## [1] "385" "672"

Stringlerin bulundugu cokumnlari bolebiliriz

library(tidyverse)
string_data <- separate(string_data, 
                        isim, 
                        into = c("first_name", 
                                 "last_name"), 
                        sep = " ")

ve birlestirebiliriz

string_data <- unite(string_data, 
                     isim, first_name, 
                     last_name, sep = " ")

Basic Text Processing

Token dedigimiz sey bir texti tekli kelimelere ve alt kelimelere indirgemekle alakalidir.

library(tokenizers)
text <- "R dersinde text process ogreniyorum"
tokens <- tokenize_words(text)
print(tokens)
## [[1]]
## [1] "r"           "dersinde"    "text"        "process"     "ogreniyorum"

Stemming dedigimiz islem kelimeleri koklerine indirger, lemmatization ise sozluk hallerine indirger

library(SnowballC)
words <- c("running", "runs", "ran")
stemmed <- wordStem(words, language = "english")
print(stemmed)
## [1] "run" "run" "ran"

Elimize bir text verisi gectigi zaman bunu analiz etmeden once icinde cok kullanilan kelimleri cikarmamiz gerekir. Bu kelimere stopwords deriz. Bu Ingilizce icin direkt paketle gelirken Turkce icin ek paket kullanmak gerekiyor.

library(tm)
text <- "This sentence will be used as 
the primary example of a stopwords removing 
tool which is important in the area of 
text analysisis and NLP"
words <- unlist(strsplit(text, " "))
cleaned <- words[!words %in% stopwords("english")]
print(cleaned)
##  [1] "This"       "sentence"   "will"       "used"       "\nthe"     
##  [6] "primary"    "example"    "stopwords"  "removing"   "\ntool"    
## [11] "important"  "area"       "\ntext"     "analysisis" "NLP"

Gordgumuz uzere cok temel kelimelerin hepsi elenmis oldu.

Iki stringin birbirine ne kadar benzer olduguna da bakabiliriz. Levenshtein Distance kac karakter edti ile bir stringin digerine donusecegini hesaplar.

library(stringdist)
## 
## Attaching package: 'stringdist'
## The following object is masked from 'package:tidyr':
## 
##     extract
stringdist("kitten", "sitting", 
           method = "lv")
## [1] 3

Cosine Similarity ise daha komplike bir yontemle 0 olmayan vektorlerin acisinin kosinusunu alip hesaplar. Yine de bize benzerlik orani verir.

library(text2vec)
text1 <- "The quick brown fox"
text2 <- "The fast brown fox"
tokens <- word_tokenizer(c(text1, text2))
it <- itoken(tokens)
v <- create_vocabulary(it)
vectorizer <- vocab_vectorizer(v)
dtm <- create_dtm(it, vectorizer)
cosine_similarity <- sim2(dtm, 
                          method = "cosine")
print(cosine_similarity)
## 2 x 2 sparse Matrix of class "dsCMatrix"
##      1    2
## 1 1.00 0.75
## 2 0.75 1.00

Date-Time Manipulation

Gün ve zaman çokça kullanacağımız bir veri tipi olacak. Özellikle analiz yaparken veya görselleştirme yaparken verilerin sıralanması açısından önemli olacak. Bunu bir veri altında düzgün ve consistent bir formatta tutmak için ise bu konuyu öğrenmek gerekir. Aynı zamanda lag değişken kullandığımız bir durum olursa bunu da date ve time üstüe kurmamız gerekebilir. R’da farklı gün-zaman verisi tipi vardır. Bunlar doğrudan gün olarak kaydettiğimiz ve bilgisayarların tarih başlangıcı olan 1970-01-01 tarihi. Bu iki farklı veri tipini de görebiliriz ancak düz kullanacağımız veri setleri büyük ihtimalle gerçek tarih ve date veri tipi ile oluşturulmuş olacak.

R’da standart olarak YYYY-MM-DD formatını kullanırız. Bu büyükten küçüğe sıraladığı için de tercih edilen bir yöntemdir.

date1 <- as.Date("2024-09-03") 

Ancak istersek tarihi oluştururken hangi formatta oluşturmak istediğimizi belirtebiliriz.

date2 <- as.Date("03/09/2024", 
                 format = "%d/%m/%Y") 

Bu tarihlerin içinden farklı bilgileri ve kısımları çekebiliriz.

format(date1, "%Y")
## [1] "2024"
weekdays(date1) 
## [1] "Tuesday"
months(date1) 
## [1] "September"
quarters(date1)
## [1] "Q3"

Date olarak oluşturmanın yanısıra 1900 başlangıçlı olan bir veri tipini de POSIX olarak oluşturabiliriz. Burada iki farklı oluşturma opsiyonu var. birisi düz obje olarak diğeriyse liste olarak oluşturma. Farklı analizler farklı yöntemler gerektirebilir, düz olarak oluşturmak daha az yer kaplarken, liste olarak oluşturmak farklı componentlara erişmek için daha rahat olabilir.

datetime1 <- as.POSIXct("2024-09-03 
                        10:15:30", 
                        format = 
                          "%Y-%m-%d 
                        %H:%M:%S")
datetime2 <- as.POSIXlt("2024-09-03 
                        10:15:30", 
                        format = "%Y-%m-%d 
                        %H:%M:%S")

Oluşturduğumuz bir karakteri de posix verisine ve date tipine çevirebiliriz.

date_1 <- "03-09-2024 10:15:30"
datetime3 <- strptime(date_1, 
                      format = "%d-%m-%Y 
                      %H:%M:%S")
datetime4 <- as.Date(date_1, 
                     format = "%d-%m-%Y 
                     %H:%M:%S")

Bunun tam tersini de yapabiliriz.

formatted_date <- format(datetime1, 
                         "%A, %B %d, %Y") 

Bu veri tipinin içinden bilgi çıkartmak için ise named list mantığında yaklaşabiliriz. Burada 1900 eklememiz veri tipi 1900 yılından donra gelen yıl sayısını saydığı içim, 1 eklememiz ise tamamen programlama ise alakalı. Bu yüzden sosyal bilimlerde çok tercih edeceğimiz bir veri saklama yöntemi değil.

datetime2$year + 1900
## [1] 2024
datetime2$mon + 1
## [1] 9
datetime2$mday 
## [1] 3

Gün ve zamanlar üstünde aritmetik işlemler de yapabiliriz.

gelecek_gun <- as.Date("2024-09-03") + 7

Fark hesaplama

gun_farkı <- difftime(as.Date("2024-09-10"), 
                      as.Date("2024-09-03"), 
                      units = "days")
gun_farkı
## Time difference of 7 days

Karşılaştırma

as.Date("2024-09-03") < as.Date("2024-09-10")
## [1] TRUE

Ikinci bir yontemle fark hesaplama. Bu cok daha detayli bir fark gosterecek.

as.duration(ymd("2024-09-16") -
              ymd("2023-11-18"))
## [1] "26179200s (~43.29 weeks)"

Daha detayli olarak zaman araligini gormek icin

start_date <- ymd("2023-01-01")
end_date <- ymd("2024-01-01")
time_interval <- interval(start_date, 
                          end_date)
print(time_interval)
## [1] 2023-01-01 UTC--2024-01-01 UTC

Belirtilen tarih o aralikta mi bakmak icin

check_date <- ymd("2023-06-01")
check_date %within% time_interval
## [1] TRUE

Timezone dönüştürme

Bu paket ileride daha çok da kullanılacak.

library(lubridate)
datetime_utc <- as.POSIXct("2024-09-03 
                           10:15:30", 
                           tz = "UTC")
datetime_istanbul <- as.POSIXct("2024-09-03 
                                10:15:30", 
                                tz = "Europe/Istanbul")
datetime_dönüşme <- with_tz(datetime_istanbul, 
                            "America/New_York")

Lubridate paketi R’da tarih ve zaman ile çalışırken en çok kullanacağımız paketlerden birisidir. Tarihler ile çalışmayı kolaylaştırır ve R’ın temel fonksiyonlarından kurtarıp daha mantıklı bir altyapıya oturtur.

date_lubri <- ymd("2024-09-04")
year(date_lubri)
## [1] 2024
month(date_lubri)
## [1] 9
wday(date_lubri, label = TRUE)
## [1] Wed
## Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat

Lubridate ile ekleme ve çıkarma yapması da çok daha kolay oluyor

last_week <- date_lubri - weeks(1)
next_month <- date_lubri + months(1)

Tarihlerle ilgili en önemli konulardan birisi de farklı gün veri formatlarını birbirine dönüştürmek. Her zaman elimizde consistent bir veri formatı olmayacak ve bunlar üstünden bir analiz yapmamız gerekirse error alacağız. Twitter verisinde bile karşılaşabileceğimiz bir durum olduğu için nasıl çözeriz önemli bir mesele oluyor.

dates_df <- data.frame(dates =c("2023-09-04", 
                                "09/04/2023", 
                                "04.09.2023", 
                                "2023-09-04 
                                14:30:00"))

Burdaki veride hem ayraç farkını hem de gün formatının giriliş farkını görmüş olduk. Bunu çözmemiz doğru bir analiz için önemli olacak. Bunu normalde çok uzun yollarla yapabiliriz ancak lubridate ile daha kolay yapmamız da mümkün.

dates_df$standard_date <- 
  as.Date(dates_df$date, 
          format = "%Y-%m-%d")
dates_df$standard_date[is.na(dates_df$
standard_date)] <- as.Date(dates_df$date[is.na(dates_df$
standard_date)], 
format = "%m/%d/%Y")

dates_df$standard_date[is.na(dates_df$standard_date)] <-
  as.Date(dates_df$date[is.na(dates_df$standard_date)], 
format = "%d.%m.%Y")
dates_df$lubridate_date <- 
  parse_date_time(dates_df$date, 
                  orders = c("ymd", "mdy", 
                             "dmy", "ymd HMS"))

Bu komut ile hem farklı formatları hem de farklı seperatörleri ve yazım formatalarını aynı anda çözebiliyoruz. Ancak bunu paket dışı yöntem ile çözmek isteseydik daha uzun olacaktı. Yukarıdaki örnekte olduğu gibi uzun uzun uğraşmak yerine çok daha rahat bir şekilde bunu çözebiliriz. Böylece tarihin olduğu bir veri ile çalışmamız çok daha kolay olacak.

SON