9  Apply Ailesi

R’de veri analizi yaparken aynı işlemi birden fazla eleman, satır, sütun, grup veya liste bileşeni üzerinde tekrarlamak çok sık karşılaşılan bir ihtiyaçtır. Örneğin bir veri setindeki bütün sayısal değişkenlerin ortalamasını almak, bir listenin her elemanına aynı fonksiyonu uygulamak, bir değişkeni gruplara göre özetlemek veya iki farklı vektörün karşılıklı elemanları üzerinde işlem yapmak isteyebiliriz.

Bu tür işlemler döngülerle yapılabilir. Ancak R, bu tekrar eden işlemleri daha kısa, daha okunabilir ve çoğu zaman daha güvenli biçimde gerçekleştirmek için apply ailesi olarak bilinen bir fonksiyon grubu sunar.

Apply ailesi tek bir fonksiyondan oluşmaz. R’de farklı veri yapıları ve farklı çıktı beklentileri için farklı apply fonksiyonları vardır. Bu bölümde özellikle şu fonksiyonlar ele alınacaktır:

Bu fonksiyonların hepsi aynı temel fikre dayanır:

Bir veri yapısının parçaları üzerinde aynı fonksiyonu tekrar tekrar uygula.

Ancak her biri farklı veri yapıları ve farklı kullanım amaçları için tasarlanmıştır. Bu nedenle apply ailesini anlamak yalnızca fonksiyon isimlerini ezberlemek değildir; hangi durumda hangi fonksiyonun daha uygun olduğunu kavramaktır.

Not

Apply ailesi, döngülerin yerine geçmek zorunda değildir. Asıl amaç, tekrar eden işlemleri daha açık, daha düzenli ve veri yapısına uygun biçimde yazabilmektir.

9.1 Neden Apply Ailesi?

Programlama öğrenirken tekrar eden işlemler için genellikle önce for döngüleri öğrenilir. Bu doğrudur; çünkü döngüler programlamanın temel yapı taşlarından biridir. Ancak R, veri analizi odaklı bir dil olduğu için birçok tekrar eden işlem vektörler, matrisler, listeler ve veri çerçeveleri üzerinde yapılır. Apply ailesi bu veri yapılarına uygun bir tekrar mantığı sağlar.

Örneğin elimizde bir matris olsun ve her sütunun ortalamasını hesaplamak isteyelim.

# Örnek bir matris oluşturalım.
notlar <- matrix(
  c(70, 80, 90,
    60, 75, 85,
    50, 65, 95),
  nrow = 3,
  byrow = TRUE
)

notlar
     [,1] [,2] [,3]
[1,]   70   80   90
[2,]   60   75   85
[3,]   50   65   95

Bu matrisin sütun ortalamalarını döngüyle hesaplayabiliriz.

# Sonuçları saklamak için boş bir vektör oluşturuyoruz.
sutun_ortalamalari <- numeric(ncol(notlar))

# Her sütun için ortalama hesaplıyoruz.
for (i in 1:ncol(notlar)) {
  sutun_ortalamalari[i] <- mean(notlar[, i])
}

sutun_ortalamalari
[1] 60.00000 73.33333 90.00000

Aynı işlemi apply() ile daha kısa ve doğrudan yazabiliriz.

# 2 değeri sütunlar üzerinde işlem yapılacağını belirtir.
apply(notlar, 2, mean)
[1] 60.00000 73.33333 90.00000

Buradaki fark yalnızca daha az kod yazmak değildir. apply() ifadesi doğrudan şunu söyler:

Bu matrisin sütunlarına mean() fonksiyonunu uygula.

Yani kodun niyeti daha açık hale gelir.

İpucu

Apply ailesi genellikle “daha hızlı” olduğu için değil, kodun niyetini daha açık ifade ettiği için tercih edilir.

9.2 Apply Ailesinin Genel Mantığı

Apply ailesindeki fonksiyonların çoğu üç temel soruya cevap verir:

  1. Hangi veri yapısı üzerinde çalışacağız?
  2. Bu veri yapısının hangi parçaları üzerinde işlem yapacağız?
  3. Her parçaya hangi fonksiyonu uygulayacağız?

Bu mantık özellikle başlangıçta karmaşık görünebilir. Ancak birkaç örnekten sonra oldukça doğal hale gelir.

Aşağıdaki tablo temel apply fonksiyonlarının kullanım alanlarını özetlemektedir.

Fonksiyon Temel kullanım alanı Tipik çıktı
apply() Matris veya array üzerinde satır/sütun işlemleri vektör, matris veya liste
lapply() Liste veya veri çerçevesinin elemanları üzerinde işlem liste
sapply() lapply() gibi çalışır, sonucu basitleştirmeye çalışır vektör, matris veya liste
vapply() sapply() benzeri; çıktı tipi önceden belirtilir güvenli ve belirli tipte çıktı
tapply() Bir vektörü gruplara göre özetlemek vektör, array veya liste
mapply() Birden fazla nesne üzerinde eş zamanlı işlem vektör, matris veya liste

Bu tabloyu ezberlemek yerine bölüm boyunca örnekler üzerinden anlamak daha faydalıdır.

9.3 apply() Fonksiyonu

apply() fonksiyonu genellikle matrisler veya çok boyutlu diziler üzerinde satır ya da sütun bazında işlem yapmak için kullanılır.

Temel söz dizimi şöyledir:

apply(X, MARGIN, FUN, ...)

Buradaki argümanlar şunlardır:

  • X: Üzerinde işlem yapılacak matris veya array.
  • MARGIN: İşlemin hangi boyutta yapılacağını belirtir.
  • FUN: Uygulanacak fonksiyon.
  • ...: Fonksiyona aktarılacak ek argümanlar.

MARGIN argümanı apply() fonksiyonunun en kritik kısmıdır.

Matrislerde:

  • MARGIN = 1 satırlar üzerinde işlem yapar.
  • MARGIN = 2 sütunlar üzerinde işlem yapar.
# Örnek bir matris oluşturalım.
m <- matrix(1:12, nrow = 3, byrow = TRUE)

m
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    5    6    7    8
[3,]    9   10   11   12

9.3.1 Satır Bazında İşlem

Satır toplamlarını hesaplayalım.

# MARGIN = 1 satırları ifade eder.
apply(m, 1, sum)
[1] 10 26 42

Her satır ayrı ayrı alınmış ve sum() fonksiyonu uygulanmıştır.

Satır ortalamalarını da benzer şekilde hesaplayabiliriz.

# Her satırın ortalaması
apply(m, 1, mean)
[1]  2.5  6.5 10.5

9.3.2 Sütun Bazında İşlem

Sütun toplamlarını hesaplamak için MARGIN = 2 kullanılır.

# MARGIN = 2 sütunları ifade eder.
apply(m, 2, sum)
[1] 15 18 21 24

Sütun ortalamaları:

# Her sütunun ortalaması
apply(m, 2, mean)
[1] 5 6 7 8
Not

apply() fonksiyonunda MARGIN = 1 satır, MARGIN = 2 sütun anlamına gelir. Bu bilgi özellikle matris işlemlerinde temel önemdedir.

9.3.3 Ek Argüman Kullanımı

Bazı fonksiyonlar ek argümanlar alır. Örneğin mean() fonksiyonunda na.rm = TRUE argümanı eksik değerleri dikkate almadan ortalama hesaplamak için kullanılır.

# Eksik değer içeren bir matris oluşturalım.
m_na <- matrix(
  c(1, 2, NA,
    4, NA, 6,
    7, 8, 9),
  nrow = 3,
  byrow = TRUE
)

m_na
     [,1] [,2] [,3]
[1,]    1    2   NA
[2,]    4   NA    6
[3,]    7    8    9

Eksik değerler dikkate alınmadan satır ortalamaları hesaplanabilir.

# mean() fonksiyonuna na.rm = TRUE argümanı aktarılır.
apply(m_na, 1, mean, na.rm = TRUE)
[1] 1.5 5.0 8.0

Burada na.rm = TRUE doğrudan apply() fonksiyonuna değil, apply() tarafından çağrılan mean() fonksiyonuna aktarılır.

9.3.4 Anonim Fonksiyon Kullanımı

apply() içinde hazır bir fonksiyon kullanılabileceği gibi, kullanıcı tarafından o anda tanımlanan kısa fonksiyonlar da kullanılabilir. Bunlara anonim fonksiyon denir. Anonim fonksiyonlar bir isme atanmaz; doğrudan ihtiyaç duyulan yerde kullanılır.

# Her satır için maksimum ve minimum değer arasındaki farkı hesaplayalım.
apply(m, 1, function(x) max(x) - min(x))
[1] 3 3 3

Burada function(x) ifadesi her satırı geçici olarak x adıyla alır ve satır içindeki en büyük değer ile en küçük değer arasındaki farkı hesaplar.

Aynı işlemi sütunlar için yapalım.

# Her sütundaki aralık değeri
apply(m, 2, function(x) max(x) - min(x))
[1] 8 8 8 8

Anonim fonksiyonlar özellikle tek satırlık kısa işlemler için oldukça kullanışlıdır.

İpucu

Anonim fonksiyonlar, kısa ve yalnızca bir kez kullanılacak işlemler için idealdir. Aynı işlem birçok yerde kullanılacaksa ayrı bir fonksiyon tanımlamak daha uygun olur.

9.3.5 apply() ve Veri Çerçeveleri

apply() fonksiyonu veri çerçeveleri üzerinde de çalışabilir; ancak dikkatli kullanılmalıdır. Çünkü veri çerçeveleri farklı veri tiplerine sahip sütunlardan oluşabilir. apply() veri çerçevesini çoğu zaman matrise dönüştürür. Bu dönüşüm beklenmeyen sonuçlara yol açabilir.

df <- data.frame(
  isim = c("Ali", "Ayşe", "Mehmet"),
  not1 = c(70, 80, 90),
  not2 = c(60, 85, 95)
)

df

Bu veri çerçevesi içinde metinsel bir sütun olduğu için apply() kullanımı dikkat gerektirir.

# Sadece sayısal sütunları seçerek apply() kullanmak daha güvenlidir.
apply(df[, c("not1", "not2")], 1, mean)
[1] 65.0 82.5 92.5
Dikkat

Veri çerçeveleri üzerinde apply() kullanırken dikkatli olunmalıdır. Veri çerçevesi matrise dönüştürüldüğünde farklı veri tipleri aynı tipe zorlanabilir.

9.4 lapply() Fonksiyonu

lapply() fonksiyonu listeler üzerinde işlem yapmak için kullanılır. Adındaki l, list kelimesinden gelir. Bu fonksiyon, bir listenin her elemanına aynı fonksiyonu uygular ve sonucu her zaman liste olarak döndürür.

Temel söz dizimi şöyledir:

lapply(X, FUN, ...)

Burada:

  • X: Liste veya liste gibi elemanlardan oluşan nesne.
  • FUN: Her elemana uygulanacak fonksiyon.
  • ...: Fonksiyona aktarılacak ek argümanlar.

Önce basit bir liste oluşturalım.

liste <- list(
  a = 1:5,
  b = 10:15,
  c = c(100, 200, 300)
)

liste
$a
[1] 1 2 3 4 5

$b
[1] 10 11 12 13 14 15

$c
[1] 100 200 300

Her elemanın ortalamasını hesaplayalım.

# lapply() her liste elemanına mean() uygular.
lapply(liste, mean)
$a
[1] 3

$b
[1] 12.5

$c
[1] 200

Sonuç liste olarak döner.

# Çıktının sınıfını inceleyelim.
class(lapply(liste, mean))
[1] "list"

Bu, lapply() fonksiyonunun en belirgin özelliğidir.

lapply() sonucu basitleştirmeye çalışmaz; daima liste döndürür.

9.4.1 Veri Çerçeveleri ile lapply()

R’de veri çerçeveleri aslında sütunlardan oluşan özel listeler olarak düşünülebilir. Bu nedenle lapply() veri çerçevesi sütunları üzerinde işlem yapmak için sık kullanılır.

# iris veri setinin ilk birkaç satırına bakalım.
head(iris)

iris veri setindeki her sütunun sınıfını öğrenelim.

# Her sütuna class() fonksiyonunu uygular.
lapply(iris, class)
$Sepal.Length
[1] "numeric"

$Sepal.Width
[1] "numeric"

$Petal.Length
[1] "numeric"

$Petal.Width
[1] "numeric"

$Species
[1] "factor"

Sayısal sütunların ortalamasını almak için önce sayısal sütunları seçelim.

# Sayısal sütunları seçiyoruz.
iris_sayisal <- iris[, 1:4]

# Her sayısal sütunun ortalaması
lapply(iris_sayisal, mean)
$Sepal.Length
[1] 5.843333

$Sepal.Width
[1] 3.057333

$Petal.Length
[1] 3.758

$Petal.Width
[1] 1.199333

9.4.2 lapply() ve Anonim Fonksiyonlar

Bir veri çerçevesindeki sayısal sütunların minimum ve maksimum değerlerini birlikte hesaplayalım.

# Her sütun için minimum ve maksimum değerleri döndürür.
lapply(iris_sayisal, function(x) c(min = min(x), max = max(x)))
$Sepal.Length
min max 
4.3 7.9 

$Sepal.Width
min max 
2.0 4.4 

$Petal.Length
min max 
1.0 6.9 

$Petal.Width
min max 
0.1 2.5 

Sonuç yine listedir. Her liste elemanı, ilgili sütunun minimum ve maksimum değerlerini içerir.

9.4.3 Ek Argüman Kullanımı

Eksik değer içeren bir liste oluşturalım.

liste_na <- list(
  a = c(1, 2, NA, 4),
  b = c(10, NA, 30),
  c = c(5, 6, 7)
)

liste_na
$a
[1]  1  2 NA  4

$b
[1] 10 NA 30

$c
[1] 5 6 7

Eksik değerleri dışarıda bırakarak ortalama hesaplayalım.

# na.rm = TRUE argümanı mean() fonksiyonuna aktarılır.
lapply(liste_na, mean, na.rm = TRUE)
$a
[1] 2.333333

$b
[1] 20

$c
[1] 6

9.5 sapply() Fonksiyonu

sapply() fonksiyonu lapply() fonksiyonuna benzer. Temel fark, sapply() fonksiyonunun çıktıyı mümkünse daha basit bir yapıya dönüştürmeye çalışmasıdır. Buradaki s, simplify kelimesinden gelir.

# lapply() liste döndürür.
lapply(liste, mean)
$a
[1] 3

$b
[1] 12.5

$c
[1] 200
# sapply() sonucu basitleştirmeye çalışır.
sapply(liste, mean)
    a     b     c 
  3.0  12.5 200.0 

İlk çıktı listedir. İkinci çıktı ise isimlendirilmiş bir vektördür.

Bu davranış pratikte kullanışlıdır; ancak bazen beklenmeyen çıktı tiplerine neden olabilir.

9.5.1 sapply() Ne Zaman Kullanışlıdır?

Eğer her elemandan tek bir değer dönmesini bekliyorsak sapply() oldukça pratiktir.

# Her elemanın uzunluğunu hesaplayalım.
sapply(liste, length)
a b c 
5 6 3 

Veri çerçevesindeki sütun sınıflarını daha okunabilir biçimde görmek için de kullanılabilir.

sapply(iris, class)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
   "numeric"    "numeric"    "numeric"    "numeric"     "factor" 

9.5.2 simplify = FALSE

sapply() fonksiyonu varsayılan olarak sonucu basitleştirir. Bu davranışı kapatmak için simplify = FALSE kullanılabilir.

# simplify = FALSE kullanıldığında sonuç liste olarak kalır.
sapply(liste, mean, simplify = FALSE)
$a
[1] 3

$b
[1] 12.5

$c
[1] 200

Bu kullanım pratikte lapply() ile benzer sonuç verir.

Dikkat

sapply() çıktı yapısını otomatik olarak basitleştirdiği için bazı durumlarda beklenmeyen sonuçlar üretebilir. Çıktı tipinin kesin olması gerekiyorsa vapply() daha güvenlidir.

9.6 vapply() Fonksiyonu

vapply() fonksiyonu, sapply() fonksiyonunun daha güvenli bir versiyonu olarak düşünülebilir. Temel fark, vapply() fonksiyonunda beklenen çıktı tipi ve uzunluğu önceden belirtilir.

Temel söz dizimi şöyledir:

vapply(X, FUN, FUN.VALUE, ...)

Burada:

  • X: Üzerinde işlem yapılacak nesne.
  • FUN: Uygulanacak fonksiyon.
  • FUN.VALUE: Her işlemden beklenen çıktı tipi ve uzunluğu.
  • ...: Ek argümanlar.

Örneğin her liste elemanından tek bir sayısal değer bekliyorsak:

# Her elemanın ortalamasını bekliyoruz.
vapply(liste, mean, numeric(1))
    a     b     c 
  3.0  12.5 200.0 

Buradaki numeric(1) ifadesi şunu söyler:

Her elemandan uzunluğu 1 olan sayısal bir çıktı bekliyorum.

Eğer fonksiyon beklenen yapıda çıktı üretmezse vapply() hata verir. Bu davranış özellikle daha güvenli ve tahmin edilebilir kod yazmak için faydalıdır.

# Bu örnekte her elemandan iki değer döndüğü için numeric(1) beklentisi bozulur.
vapply(liste, function(x) c(min(x), max(x)), numeric(1))
Error in vapply(liste, function(x) c(min(x), max(x)), numeric(1)): values must be length 1,
 but FUN(X[[1]]) result is length 2

Doğru kullanım:

# Her elemandan iki sayısal değer beklediğimizi belirtiyoruz.
vapply(liste, function(x) c(min = min(x), max = max(x)), numeric(2))
    a  b   c
min 1 10 100
max 5 15 300
İpucu

Paket veya fonksiyon geliştirirken vapply() daha güvenli bir tercih olabilir. Çünkü çıktı yapısını açık biçimde belirtmenizi sağlar.

9.7 tapply() Fonksiyonu

tapply() fonksiyonu bir vektörü bir veya daha fazla gruba göre özetlemek için kullanılır. Adındaki t, tarihsel olarak table veya tapply yapısına gönderme yapar. Pratikte tapply() fonksiyonunu gruplandırılmış özet işlemleri için düşünebiliriz.

Temel söz dizimi şöyledir:

tapply(X, INDEX, FUN, ...)

Burada:

  • X: Özetlenecek vektör.
  • INDEX: Grupları belirleyen factor veya factor listesi.
  • FUN: Her gruba uygulanacak fonksiyon.
  • ...: Ek argümanlar.

Basit bir örnek oluşturalım.

# Öğrencilerin bölümleri ve notları
bolum <- c("İstatistik", "İstatistik", "Matematik", "Matematik", "İstatistik")
not <- c(80, 70, 60, 90, 85)

# Bölüme göre ortalama not
tapply(not, bolum, mean)
İstatistik  Matematik 
  78.33333   75.00000 

Burada not vektörü bolum değişkenine göre gruplanmış ve her grup için ortalama hesaplanmıştır.

9.7.1 Birden Fazla Grup ile tapply()

Birden fazla gruplama değişkeni de kullanılabilir.

cinsiyet <- c("Kadın", "Erkek", "Kadın", "Erkek", "Kadın")

# Bölüm ve cinsiyete göre ortalama not
tapply(not, list(bolum, cinsiyet), mean)
           Erkek Kadın
İstatistik    70  82.5
Matematik     90  60.0

Sonuç iki boyutlu bir tablo gibi görünür.

9.7.2 Eksik Değerlerle Kullanım

not_na <- c(80, 70, NA, 90, 85)

# Eksik değerleri dışarıda bırakarak ortalama
tapply(not_na, bolum, mean, na.rm = TRUE)
İstatistik  Matematik 
  78.33333   90.00000 
Not

tapply() özellikle base R ile gruplara göre özet istatistik üretmek için kullanışlıdır. dplyr paketindeki group_by() ve summarise() mantığına geçmeden önce bu yapı iyi anlaşılmalıdır.

9.8 mapply() Fonksiyonu

mapply() fonksiyonu, bir fonksiyonu birden fazla nesnenin karşılıklı elemanları üzerinde uygulamak için kullanılır. Adındaki m, multivariate veya multiple fikrine işaret eder.

Basit bir örnekle başlayalım.

# İki vektör oluşturalım.
x <- c(1, 2, 3)
y <- c(10, 20, 30)

# Karşılıklı elemanları toplayalım.
mapply(sum, x, y)
[1] 11 22 33

Bu işlem şu hesaplamaları yapar:

  • sum(1, 10)
  • sum(2, 20)
  • sum(3, 30)

9.8.1 Parametreli Fonksiyonlarda mapply()

Bir vektördeki değerleri farklı üslerle yükseltelim.

taban <- c(2, 3, 4)
us <- c(2, 3, 2)

# Her tabanı karşılık gelen üsse yükseltir.
mapply(function(a, b) a^b, taban, us)
[1]  4 27 16

9.8.2 Karakter Üretiminde mapply()

ad <- c("Ali", "Ayşe", "Mehmet")
not <- c(80, 90, 75)

# Her öğrenci için etiket oluşturalım.
mapply(function(a, n) paste(a, "notu:", n), ad, not)
              Ali              Ayşe            Mehmet 
   "Ali notu: 80"   "Ayşe notu: 90" "Mehmet notu: 75" 

Bu tür kullanım, raporlama ve dinamik metin üretimi için faydalıdır.

İpucu

Bir fonksiyonun birden fazla girdisi varsa ve bu girdilerin karşılıklı elemanları üzerinde işlem yapmak istiyorsanız mapply() iyi bir seçenektir.

9.9 Apply Ailesi ve Döngüler

Apply ailesi bazen “döngü kullanmadan işlem yapmak” şeklinde anlatılır. Bu ifade kısmen doğrudur; ancak eksiktir. Çünkü apply fonksiyonları da arka planda tekrar eden işlemler gerçekleştirir. Fark, bu tekrar mantığının daha yüksek seviyeli ve veri yapısına uygun biçimde ifade edilmesidir.

Aşağıdaki iki kod aynı amaca hizmet eder.

# Döngü ile sütun ortalamaları
sonuc_for <- numeric(ncol(iris_sayisal))

for (i in 1:ncol(iris_sayisal)) {
  sonuc_for[i] <- mean(iris_sayisal[[i]])
}

sonuc_for
[1] 5.843333 3.057333 3.758000 1.199333
# sapply ile sütun ortalamaları
sonuc_apply <- sapply(iris_sayisal, mean)

sonuc_apply
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333 

İkinci kullanımda kod daha kısa ve niyet daha açıktır. Ancak bazı durumlarda for döngüsü daha okunabilir olabilir. Özellikle karmaşık işlemler, ara kontroller veya birden fazla adım içeren süreçlerde döngü kullanmak daha anlaşılır olabilir.

Önemli

Apply ailesi döngülerin düşmanı değildir. Döngüler ve apply fonksiyonları farklı durumlarda farklı avantajlar sağlar.

9.10 Hangi Apply Fonksiyonu Ne Zaman Kullanılır?

Aşağıdaki tablo karar vermeyi kolaylaştırabilir.

İhtiyaç Uygun fonksiyon
Matrisin satır veya sütunlarına işlem uygulamak apply()
Listenin her elemanına işlem uygulamak lapply()
Liste sonucunu vektör/matris gibi daha basit yapıya çevirmek sapply()
Çıktı tipini güvence altına almak vapply()
Bir vektörü gruplara göre özetlemek tapply()
Birden fazla vektörün karşılıklı elemanlarıyla işlem yapmak mapply()

Bu tablo kesin kurallar değil, pratik yönlendirmeler sunar. Gerçek kullanımda veri yapısı, beklenen çıktı ve kodun okunabilirliği birlikte değerlendirilmelidir.

9.11 Mini Vaka Çalışması: Sınav Sonuçlarını Özetleme

Şimdi apply ailesindeki bazı fonksiyonları küçük bir örnek üzerinde birlikte kullanalım.

# Öğrenci bazlı sınav sonuçları
sinav <- data.frame(
  ogrenci = c("Ali", "Ayşe", "Mehmet", "Zeynep", "Can", "Elif"),
  bolum = c("İstatistik", "İstatistik", "Matematik", "Matematik", "İstatistik", "Matematik"),
  vize = c(70, 85, 60, 90, 75, 65),
  final = c(80, 90, 55, 95, 70, 75)
)

sinav

Öğrencilerin yıl sonu notunu hesaplayalım.

# Vize %40, final %60 ağırlıklı olsun.
sinav$yil_sonu <- 0.4 * sinav$vize + 0.6 * sinav$final

sinav

Sayısal değişkenlerin özetini alalım.

# Sayısal sütunları seçelim.
sinav_sayisal <- sinav[, c("vize", "final", "yil_sonu")]

# Her sayısal sütun için ortalama
sapply(sinav_sayisal, mean)
    vize    final yil_sonu 
74.16667 77.50000 76.16667 

Her öğrenci için vize ve final arasındaki farkı hesaplayalım.

# apply() satır bazında işlem yapar.
apply(sinav[, c("vize", "final")], 1, function(x) x[2] - x[1])
[1] 10  5 -5  5 -5 10

Bölümlere göre yıl sonu ortalaması:

# tapply() ile gruplara göre ortalama
tapply(sinav$yil_sonu, sinav$bolum, mean)
İstatistik  Matematik 
  78.66667   73.66667 

Öğrenciler için kısa açıklama metni oluşturalım.

# mapply() ile karşılıklı elemanlardan metin üretimi
mapply(
  function(ad, not) paste(ad, "yıl sonu notu:", not),
  sinav$ogrenci,
  sinav$yil_sonu
)
                       Ali                       Ayşe 
   "Ali yıl sonu notu: 76"   "Ayşe yıl sonu notu: 88" 
                    Mehmet                     Zeynep 
"Mehmet yıl sonu notu: 57" "Zeynep yıl sonu notu: 93" 
                       Can                       Elif 
   "Can yıl sonu notu: 72"   "Elif yıl sonu notu: 71" 

Bu küçük vaka çalışması, apply ailesinin yalnızca soyut fonksiyonlardan oluşmadığını; veri özetleme, raporlama ve tekrar eden işlemleri düzenleme açısından pratik bir araç seti sunduğunu göstermektedir.

9.12 Sık Yapılan Hatalar

9.12.1 apply() Fonksiyonunu Her Veri Çerçevesinde Güvenle Kullanmak

Veri çerçeveleri farklı veri tiplerine sahip olabilir. apply() veri çerçevesini matrise dönüştürebileceği için sayısal olmayan sütunlar sonucu etkileyebilir.

df_hata <- data.frame(
  grup = c("A", "B", "C"),
  x = c(10, 20, 30),
  y = c(5, 15, 25)
)

# Bu kullanım veri tipleri nedeniyle beklenmeyen sonuçlar verebilir.
apply(df_hata, 2, class)
       grup           x           y 
"character" "character" "character" 

Daha güvenli kullanım:

# Sayısal sütunları seçerek işlem yapmak daha uygundur.
apply(df_hata[, c("x", "y")], 2, mean)
 x  y 
20 15 

9.12.2 sapply() Çıktısını Her Zaman Aynı Sanmak

sapply() sonucu otomatik basitleştirmeye çalışır. Her elemandan farklı uzunlukta çıktı dönerse sonuç liste olabilir.

karisik <- list(
  a = 1:3,
  b = 1:5
)

sapply(karisik, function(x) x[1:2])
     a b
[1,] 1 1
[2,] 2 2

Ancak farklı uzunlukta çıktı üretildiğinde yapı değişebilir.

sapply(karisik, function(x) x)
$a
[1] 1 2 3

$b
[1] 1 2 3 4 5

Çıktı tipinin kesin olması gerekiyorsa vapply() düşünülmelidir.

9.12.3 MARGIN Değerini Karıştırmak

apply() fonksiyonunda MARGIN = 1 satır, MARGIN = 2 sütun anlamına gelir. Bu karışıklık sık görülür.

m
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    5    6    7    8
[3,]    9   10   11   12
# Satır toplamları
apply(m, 1, sum)
[1] 10 26 42
# Sütun toplamları
apply(m, 2, sum)
[1] 15 18 21 24

9.12.4 Ek Argümanların Nereye Gittiğini Karıştırmak

# na.rm = TRUE mean() fonksiyonuna aktarılır.
apply(m_na, 1, mean, na.rm = TRUE)
[1] 1.5 5.0 8.0

Burada na.rm = TRUE, apply() fonksiyonunun değil, mean() fonksiyonunun argümanıdır.

9.13 Egzersizler

Aşağıdaki egzersizler apply ailesinin temel kullanımını pekiştirmek için hazırlanmıştır.

  1. 4 satır ve 5 sütundan oluşan sayısal bir matris oluşturunuz.
  2. Bu matrisin satır toplamlarını apply() ile hesaplayınız.
  3. Bu matrisin sütun ortalamalarını apply() ile hesaplayınız.
  4. Bir liste oluşturunuz ve her elemanın uzunluğunu lapply() ile hesaplayınız.
  5. Aynı işlemi sapply() ile yapınız ve çıktı farkını inceleyiniz.
  6. iris veri setindeki sayısal sütunların ortalamasını sapply() ile hesaplayınız.
  7. Aynı işlemi vapply() ile yapınız.
  8. Bir not vektörünü bölüm değişkenine göre tapply() ile özetleyiniz.
  9. İki vektörü kullanarak mapply() ile dinamik etiketler üretiniz.
  10. apply() fonksiyonunda MARGIN = 1 ve MARGIN = 2 farkını kendi örneğinizle açıklayınız.

9.14 Egzersiz Çözümleri

# 1. Matris oluşturma
mat <- matrix(1:20, nrow = 4, byrow = TRUE)

mat
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    6    7    8    9   10
[3,]   11   12   13   14   15
[4,]   16   17   18   19   20
# 2. Satır toplamları
apply(mat, 1, sum)
[1] 15 40 65 90
# 3. Sütun ortalamaları
apply(mat, 2, mean)
[1]  8.5  9.5 10.5 11.5 12.5
# 4. Liste oluşturma ve lapply kullanımı
ornek_liste <- list(
  a = 1:5,
  b = letters[1:3],
  c = c(TRUE, FALSE, TRUE, TRUE)
)

lapply(ornek_liste, length)
$a
[1] 5

$b
[1] 3

$c
[1] 4
# 5. Aynı işlemi sapply ile yapma
sapply(ornek_liste, length)
a b c 
5 3 4 
# 6. iris veri setindeki sayısal sütunların ortalaması
iris_sayisal <- iris[, 1:4]

sapply(iris_sayisal, mean)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333 
# 7. Aynı işlem vapply ile
vapply(iris_sayisal, mean, numeric(1))
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333 
# 8. Bölüme göre not ortalaması
bolum <- c("A", "A", "B", "B", "A")
not <- c(70, 80, 60, 90, 85)

tapply(not, bolum, mean)
       A        B 
78.33333 75.00000 
# 9. mapply ile etiket üretimi
ad <- c("Ali", "Ayşe", "Mehmet")
puan <- c(75, 90, 60)

mapply(function(a, p) paste(a, "-", p, "puan"), ad, puan)
               Ali               Ayşe             Mehmet 
   "Ali - 75 puan"   "Ayşe - 90 puan" "Mehmet - 60 puan" 
# 10. MARGIN farkı
mat
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    6    7    8    9   10
[3,]   11   12   13   14   15
[4,]   16   17   18   19   20
# Satır bazında maksimum değerler
apply(mat, 1, max)
[1]  5 10 15 20
# Sütun bazında maksimum değerler
apply(mat, 2, max)
[1] 16 17 18 19 20

9.15 Bölüm Özeti

Apply ailesi, R’de tekrar eden işlemleri veri yapısına uygun biçimde ifade etmeyi sağlayan önemli bir fonksiyon grubudur. Bu bölümde apply(), lapply(), sapply(), vapply(), tapply() ve mapply() fonksiyonları ele alındı.

apply() daha çok matris ve array yapılarında satır veya sütun bazlı işlemler için kullanılır. lapply() liste elemanları üzerinde işlem yapar ve her zaman liste döndürür. sapply() benzer şekilde çalışır; ancak çıktıyı basitleştirmeye çalışır. vapply() beklenen çıktı tipini önceden belirterek daha güvenli bir yapı sunar. tapply() gruplara göre özetleme yapmak için kullanılır. mapply() ise birden fazla nesnenin karşılıklı elemanları üzerinde işlem yapmayı sağlar.

Apply ailesini öğrenirken önemli olan hangi fonksiyonun hangi veri yapısı ve hangi çıktı beklentisi için uygun olduğunu anlamaktır. Bu fonksiyonlar döngülerin yerine geçmek zorunda değildir; ancak birçok veri analizi probleminde kodu daha kısa, daha düzenli ve daha okunabilir hale getirir.