2.Hafta derste üç ana başlık altında çalıştık:
veri setinin R ortamına aktarılması ve betimsel incelenmesi,
eksik veri analizi ile eksik verilerle başa çıkma yöntemleri,
uç değer tespiti, normallik varsayımı kontrolü ve çoklu bağlantı analizi
İlk aşamada haven paketi ile SPSS formatındaki
SCREEN.sav dosyası R’a aktardık. Veri setini tanımak için
head(), summary() ve
psych::describe() fonksiyonlarıyla temel yapıyı incelendik.
Betimsel istatistikleri daha düzenli sunmak adına
gtsummary, vtable ve skimr gibi
farklı paketler de denedik. Sonra da DataExplorer::create_report() ile
veri setinin otomatik profilleme raporu oluşturduk. Rapor ile verinin
genel yapısını, dağılımlarını ve olası sorunlu noktaları hızlıca
görmemizi sağladı.
İkinci aşamada naniar paketi ile eksik veri durumu
detaylıca inceledik. any_na(), n_miss(),
prop_miss(), miss_var_summary() ve
miss_var_table() fonksiyonları ile eksik verilerin hangi
değişkenlerde yoğunlaştığı belirledik. SCREEN verisinde özellikle INCOME
değişkeninde 26 (%5.59) ve ATTHOUSE değişkeninde 1 (%0.22) eksik gözlem
bulunduğunu tespit ettik. Çünkü yüzdeyi öğrendikten sonra ona göre kayıp
veri ile baş edeceğiz (Örn. MCAR ve %5 altında ise sileceğiz gibi.).
Eksik verilerin mekanizmasını anlamak için mcar_test()
ile Little’ın MCAR testi uyguladık Sonuç anlamlı çıktı (p = .044), yani
eksiklikler tamamen rastlantısal değil (MCAR değilmiş MAR ve MNAR tespit
edemiyorduk zaten tam olarak).
Eksik veriyle başa çıkma yöntemlerinden üçünü uyguladık ve denedik:
na.omit()): Eksik
verili satırların tamamen silinmesi; örneklem kaybına yol açar.mice paketi ile 5 tamamlanmış veri seti (m=5) oluşturularak
PMM (Predictive Mean Matching) yöntemi uygulandı. with() ve
pool() fonksiyonları ile sonuçlar birleştirildi. (Şu an en
kabul göreni buydu)library(naniar)
miss_var_summary(screen)
miss_var_table(screen)
mcar_test(data = screen[, c(2,3,4,5,7,8)])
screen$INCOME[is.na(screen$INCOME)] <- mean(screen$INCOME, na.rm = TRUE)
library(mice)
imputed_data <- mice(screen, m = 5, maxit = 50, method = 'pmm', seed = 500)
fit <- with(imputed_data, lm(TIMEDRS ~ ATTHOUSE + INCOME))
pooled_results <- pool(fit)
summary(pooled_results)Üçüncü aşamada veri temizliği ve varsayım kontrolleri yapmışız derste
tam takip edememiştim. Tek değişkenli uç değerler için
outliers::scores() fonksiyonu ile z-puanları hesaplanıyor.
±3 sınırını aşan değerler inceledik.. ggplot2 ile histogram
ve boxplot grafikleri çizdik (Hatırladığım en pratik kod kümesi);
plotly ile interaktif boxplot’lar üzerinden uç değerlerin
ID numaraları belirlemişiz. Çok değişkenli uç değerler
için mahalanobis() uzaklığı hesapladık α = .001 düzeyinde
Ki-kare kritik değeri belirlenerek bu değeri aşan 9 gözlemi veriden
çıkarıldı. Çoklu bağlantı için VIF değerleri
hesaplandı. Tüm değişkenlerde VIF < 5 olduğu görüldü, dolayısıyla
çoklu bağlantı sorunu yoktu.
library(outliers)
z.scores <- screen %>% select(2:5) %>% scores(type = "z") %>% round(2)
veri <- screen[, 1:5]
md <- mahalanobis(veri, center = colMeans(veri), cov = cov(veri))
cutoff <- qchisq(p = 1 - 0.001, df = ncol(veri))
ucdegerler <- which(md > cutoff)
model <- lm(SUBNO ~ TIMEDRS + ATTDRUG + ATTHOUSE + INCOME + RACE + MSTATUS, data = screen)
library(olsrr)
ols_vif_tol(model)Derste öğrendiğimiz tüm aşamaları, R’ın kendi airquality
veri seti üzerinde uygulayacağım (Bu veri setini kullanmayı chat önerdi
çünkü eksik veri de içeren ama kontrol edebileceğim veri seti gelmedi
aklıma. İlk dönemki derste proje ödevindeki kullandığım veri seti çok
teferruatlıydı onu kullansaydım vei setindeki değişkenleri kavrama
çalışana kadar kodları deneyemeyecektim.)
CHAT: “Bu veri seti New York’ta 1973 yılında Mayıs-Eylül ayları arasında günlük hava kalitesi ölçümlerini içermektedir. Ozon değişkeninin sağa çarpık olması ve doğal eksik veriler içermesi, derste işlediğiniz konuları denemek için uygun veri setlerinden birisidir.”
## Ozone Solar.R Wind Temp Month Day
## 1 41 190 7.4 67 5 1
## 2 36 118 8.0 72 5 2
## 3 12 149 12.6 74 5 3
## 4 18 313 11.5 62 5 4
## 5 NA NA 14.3 56 5 5
## 6 28 NA 14.9 66 5 6
## 'data.frame': 153 obs. of 6 variables:
## $ Ozone : int 41 36 12 18 NA 28 23 19 8 NA ...
## $ Solar.R: int 190 118 149 313 NA NA 299 99 19 194 ...
## $ Wind : num 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
## $ Temp : int 67 72 74 62 56 66 65 59 61 69 ...
## $ Month : int 5 5 5 5 5 5 5 5 5 5 ...
## $ Day : int 1 2 3 4 5 6 7 8 9 10 ...
## vars n mean sd median trimmed mad min max range skew
## Ozone 1 116 42.13 32.99 31.5 37.80 25.95 1.0 168.0 167 1.21
## Solar.R 2 146 185.93 90.06 205.0 190.34 98.59 7.0 334.0 327 -0.42
## Wind 3 153 9.96 3.52 9.7 9.87 3.41 1.7 20.7 19 0.34
## Temp 4 153 77.88 9.47 79.0 78.28 8.90 56.0 97.0 41 -0.37
## Month 5 153 6.99 1.42 7.0 6.99 1.48 5.0 9.0 4 0.00
## Day 6 153 15.80 8.86 16.0 15.80 11.86 1.0 31.0 30 0.00
## kurtosis se
## Ozone 1.11 3.06
## Solar.R -1.00 7.45
## Wind 0.03 0.28
## Temp -0.46 0.77
## Month -1.32 0.11
## Day -1.22 0.72
CHAT: “Veri seti 153 gözlem ve 6 değişkenden oluşuyor. Değişkenler: Ozone (ozon konsantrasyonu, ppb), Solar.R (güneş radyasyonu, lang), Wind (rüzgar hızı, mph), Temp (sıcaklık, °F), Month ve Day.”
## [1] TRUE
## [1] 44
## [1] 0.0479
## # A tibble: 6 × 3
## variable n_miss pct_miss
## <chr> <int> <num>
## 1 Ozone 37 24.2
## 2 Solar.R 7 4.58
## 3 Wind 0 0
## 4 Temp 0 0
## 5 Month 0 0
## 6 Day 0 0
## # A tibble: 3 × 3
## n_miss_in_var n_vars pct_vars
## <int> <int> <dbl>
## 1 0 4 66.7
## 2 7 1 16.7
## 3 37 1 16.7
## # A tibble: 3 × 3
## n_miss_in_case n_cases pct_cases
## <int> <int> <dbl>
## 1 0 111 72.5
## 2 1 40 26.1
## 3 2 2 1.31
Ozone değişkeninde ve Solar.R değişkeninde eksik gözlem bulunuyor. Wind, temp, month ve day değişkenlerinde ise hiç eksiklik yokmuş.
UpSet grafiğinde eksik verilerin büyük kısmının sadece Ozone değişkeninde tek başına gerçekleştiğini, bir kısmının ise hem Ozone hem Solar.R’da birlikte eksik olduğunu görüyoruz. Bu önemli çünkü belki problem durumuna göre ikili doldurmalar yapılabilir ama tabii o zaman farklı veri setleri ile alt problemelere bakıldığı için geçerli olmayacaktır, bakacağız.
## # A tibble: 1 × 4
## statistic df p.value missing.patterns
## <dbl> <dbl> <dbl> <int>
## 1 35.1 14 0.00142 4
DERS NOT: Little’ın MCAR testinin sonucu p değerine göre yorumlanır. p > .05 ise eksiklikler tamamen rastlantısaldır (MCAR); p < .05 ise veriler MCAR değildir ve eksikliklerin sistematik bir yapıda olabileceği düşünülmelidir.
## Orijinal veri: 153 gözlem
## Listwise sonrası: 111 gözlem
## Kaybedilen gözlem: 42
## Orijinal Ozone SD: 33
veri_ort$Ozone[is.na(veri_ort$Ozone)] <- mean(veri_ort$Ozone, na.rm = TRUE)
veri_ort$Solar.R[is.na(veri_ort$Solar.R)] <- mean(veri_ort$Solar.R, na.rm = TRUE)
cat("Ort doldurma sonrası Ozone SD:", round(sd(veri_ort$Ozone), 2), "\n")## Ort doldurma sonrası Ozone SD: 28.7
## [1] FALSE
Beklendiği gibi, ortalama ile doldurma standart sapmayı düşürdü. Bu yöntem varyansı küçülttüğü için ideal değildi ama medyanda daha az etki etmesi lazımdı onu deneyeceğim hemen altta: (Olmadı aynı çıktı ?)
veri_medyan <- veri
veri_medyan$Ozone[is.na(veri_medyan$Ozone)] <- mean(veri_medyan$Ozone, na.rm = TRUE)
veri_medyan$Solar.R[is.na(veri_medyan$Solar.R)] <- mean(veri_medyan$Solar.R, na.rm = TRUE)
cat("Medyan doldurma sonrası Ozone SD:", round(sd(veri_medyan$Ozone), 2), "\n")## Medyan doldurma sonrası Ozone SD: 28.7
## [1] FALSE
## Ort doldurma sonrası Ozone SD: 28.7
## Medyan doldurma sonrası Ozone SD: 28.7
## Wind Temp Month Day Solar.R Ozone
## 111 1 1 1 1 1 1 0
## 35 1 1 1 1 1 0 1
## 5 1 1 1 1 0 1 1
## 2 1 1 1 1 0 0 2
## 0 0 0 0 7 37 44
## term estimate std.error statistic df p.value
## 1 (Intercept) -63.7747 21.5203 -2.96 50.3 4.64e-03
## 2 Solar.R 0.0619 0.0218 2.84 63.0 6.01e-03
## 3 Wind -2.9529 0.6373 -4.63 38.7 4.00e-05
## 4 Temp 1.5716 0.2411 6.52 44.9 5.37e-08
Çoklu atama ile oluşturulan 5 veri seti üzerinden regresyon modeli
kurdum ve pool() fonksiyonu ile sonuçlar birleştirdim. PMM
yöntemi, sürekli değişkenler için en sık önerilen atama yöntemi olarak
geçiyor
sayisal_degiskenler <- veri_temiz %>% select(Ozone, Solar.R, Wind, Temp)
z_scores <- scores(sayisal_degiskenler, type = "z") %>% round(2)
head(z_scores)## Ozone Solar.R Wind Temp
## 1 -0.04 0.05 -0.73 -1.15
## 2 -0.21 -0.77 -0.56 -0.62
## 3 -1.05 -0.42 0.75 -0.41
## 4 -0.84 1.44 0.44 -1.68
## 5 0.00 0.00 1.23 -2.31
## 6 -0.49 0.00 1.40 -1.26
data.frame(
Degisken = names(sayisal_degiskenler),
Min_Z = round(apply(z_scores, 2, min), 2),
Max_Z = round(apply(z_scores, 2, max), 2)
) %>% kable()| Degisken | Min_Z | Max_Z | |
|---|---|---|---|
| Ozone | Ozone | -1.43 | 4.39 |
| Solar.R | Solar.R | -2.03 | 1.68 |
| Wind | Wind | -2.34 | 3.05 |
| Temp | Temp | -2.31 | 2.02 |
ggplot(veri_temiz, aes(x = Ozone)) +
geom_histogram(aes(y = ..density..), bins = 25, fill = "pink", alpha = 0.7) +
geom_density(fill = "pink", alpha = 0.3) +
geom_vline(xintercept = mean(veri_temiz$Ozone), color = "red", linetype = "dashed") +
annotate("text", x = mean(veri_temiz$Ozone) + 10, y = 0.015,
label = paste("Ort =", round(mean(veri_temiz$Ozone), 1)), color = "red") +
theme_minimal() +
labs(title = "Ozone Değişkeninin Dağılımı", x = "Ozone (ppb)", y = "Yoğunluk")Ozone değişkeni derste incelediğimiz TIMEDRS’e benzer şekilde sağa çarpık bir dağılım sergiliyor.
ggplot(veri_temiz, aes(y = Ozone)) +
geom_boxplot(fill = "pink", alpha = 0.5) +
theme_minimal() +
labs(title = "Ozone boxplot", y = "Ozone")plot_ly(y = veri_temiz$Ozone, type = 'box', name = "Ozone") %>%
layout(title = 'Ozone interaktiff boxplot')ggplot(veri_temiz, aes(x = factor(Month), y = Ozone, fill = factor(Month))) +
geom_boxplot() +
theme_minimal() +
labs(title = "Aylara göre Ozone", x = "Ay", y = "Ozone", fill = "Ay")md <- mahalanobis(sayisal_degiskenler,
center = colMeans(sayisal_degiskenler),
cov = cov(sayisal_degiskenler))
alpha <- 0.001
kritik_deger <- qchisq(p = 1 - alpha, df = ncol(sayisal_degiskenler))
cat("Ki-kare kritik değeri:", round(kritik_deger, 2), "\n")## Ki-kare kritik değeri: 18.5
uc_indexler <- which(md > kritik_deger)
cat("Çok değişkenli uç değer sayısı:", length(uc_indexler), "\n")## Çok değişkenli uç değer sayısı: 1
if (length(uc_indexler) > 0) {
veri_temiz[uc_indexler, c("Ozone", "Solar.R", "Wind", "Temp")] %>% kable()
}| Ozone | Solar.R | Wind | Temp | |
|---|---|---|---|---|
| 117 | 168 | 238 | 3.4 | 81 |
if (length(uc_indexler) > 0) {
veri_son <- veri_temiz[-uc_indexler, ]
cat("Uç değerler çıkarıldıktan sonra:", nrow(veri_son), "gözlem\n")
} else {
veri_son <- veri_temiz
cat("Çıkarılacak uç değer bulunamadı.\n")
}## Uç değerler çıkarıldıktan sonra: 152 gözlem
carpiklik_df <- data.frame(
Degisken = c("Ozone", "Solar.R", "Wind", "Temp"),
Carpiklik = c(skewness(veri_son$Ozone), skewness(veri_son$Solar.R),
skewness(veri_son$Wind), skewness(veri_son$Temp)),
Basiklik = c(kurtosis(veri_son$Ozone), kurtosis(veri_son$Solar.R),
kurtosis(veri_son$Wind), kurtosis(veri_son$Temp))
)
carpiklik_df %>% mutate(across(where(is.numeric), ~round(., 2))) %>% kable()| Degisken | Carpiklik | Basiklik |
|---|---|---|
| Ozone | 1.13 | 4.14 |
| Solar.R | -0.42 | 2.11 |
| Wind | 0.36 | 3.09 |
| Temp | -0.37 | 2.55 |
ggplot(veri_son, aes(sample = Ozone)) +
geom_qq() +
geom_qq_line(color = "pink") +
theme_minimal() +
labs(title = "Ozone QQ Plot")ggplot(veri_son, aes(sample = Wind)) +
geom_qq() +
geom_qq_line(color = "pink") +
theme_minimal() +
labs(title = "Wind QQ Plot")Ozone değişkeni sağa çarpık olduğu için log dönüşüm uygulayalım (derste TIMEDRS için yapmıştık):
## Dönüşüm öncesi:
## vars n mean sd median trimmed mad min max range skew kurtosis se
## X1 1 152 41.3 26.9 42.1 38 28.4 1 135 134 1.12 1.08 2.18
##
## Dönüşüm sonrası:
## vars n mean sd median trimmed mad min max range skew kurtosis se
## X1 1 152 3.53 0.71 3.76 3.57 0.64 0.69 4.91 4.22 -0.69 0.86 0.06
par(mfrow = c(1, 2))
hist(veri_son$Ozone, col = "pink", main = "Orijinal Ozone", xlab = "Ozone")
hist(veri_son$log_Ozone, col = "pink", main = "Log(Ozone + 1)", xlab = "Log Ozone")Log dönüşüm sonrası çarpıklık belirgin şekilde azalmış, dağılım normale daha çok yaklaştı.
kor_matris <-
cor(veri_son[, c("Ozone", "Solar.R", "Wind", "Temp")])
round(kor_matris, 2) %>% kable()| Ozone | Solar.R | Wind | Temp | |
|---|---|---|---|---|
| Ozone | 1.00 | 0.31 | -0.52 | 0.64 |
| Solar.R | 0.31 | 1.00 | -0.05 | 0.26 |
| Wind | -0.52 | -0.05 | 1.00 | -0.46 |
| Temp | 0.64 | 0.26 | -0.46 | 1.00 |
corrplot(kor_matris, method = "number", type = "upper",
tl.col = "darkred", tl.srt = 45,
title = "Korelasyon matrisi", mar = c(0,0,1,0))2.Haftaki derste, çok değişkenli istatistik analizlerinin ön koşulu
olan veri hazırlama sürecini öğrendik. Öğrendiğim en önemli şey, analiz
yapmadan önce verinin “analiz edilebilir” duruma getirilmesinin ne kadar
sistematik bir süreç olduğuydu. Özellikle eksik veri konusunda dikkatimi
çeken nokta, listwise deletion’ın ne kadar tehlikeli olabileceğiydi.
airquality verisinde bile 42 gözlem kaybediliyordu bu toplam verinin
yaklaşık %27’si demek. mice paketi ile yapılan çoklu atama
çok daha güvenilir bir yöntem olarak öne çıkıyor; bireyin diğer
yanıtlarına en çok benzeyen kişilere bakılarak eksik veriler tahmin
ediliyor. Uç değer tespitinde tek değişkenli (z-puanları) ve çok
değişkenli yaklaşımları birlikte kullanmanın gerekliliğini kavradım. Bir
gözlem tek değişkende normal sınırlarda olsa bile, birden fazla değişken
birlikte ele alındığında uç değer olarak ortaya çıkabiliyor. Normallik
testleri konusunda, Kolmogorov-Smirnov gibi örneklem büyüklüğünden çok
etkilenen testler yerine Jarque-Bera testinin ve çarpıklık/basıklık
değerlerinin daha güvenilir olduğunu öğrendim ama Jarqur-Bera testini
deneyemedim haftaya onu da deneyeceğim. Yorumlamasını becerememem gibi
geldi ve basıklık çarpıklık alışkın olduğum ve kolay olduğu için onlara
baktım. Sağa çarpık dağılımlarda log dönüşümün işe yaradığını uygulamalı
olarak gördüm. naniar paketinin eksik veri analizindeki
kullanım kolaylığı gerçekten etkileyici. Özellikle
vis_miss() ve gg_miss_upset() ile eksik
verilerin dağılımını görselleştirmek, sayısal tablolardan çok daha
anlaşılır.