Think Stats kitabının üçüncü baskısını Bookshop.org ve Amazon üzerinden edinebilirsiniz (bunlar satış ortaklığı bağlantılarıdır). Ücretsiz çevrimiçi sürümü beğeniyorsanız bana bir kahve ısmarlamayı düşünebilirsiniz.

3. Olasılık kütle fonksiyonları#

Önceki bölümde dağılımları, bir değer kümesiyle bu değerlerin frekanslarını, yani her değerin görülme sayısını içeren FreqTab nesnesiyle temsil ettik. Bu bölümde dağılımları betimlemenin başka bir yolunu, olasılık kütle fonksiyonunu (PMF) tanıtacağız.

Bir PMF’yi temsil etmek için değerlerden ve bunların olasılıklarından oluşan Pmf nesnesini kullanacağız. Dağılımın ortalamasını ve varyansını, ayrıca dağılımın sola mı sağa mı çarpık olduğunu gösteren çarpıklığı hesaplamak için Pmf nesnelerinden yararlanacağız. Son olarak “inceleme paradoksu” denen olgunun, bir örneklemin dağılıma ilişkin yanlı bir görünüm sunmasına nasıl yol açabileceğini inceleyeceğiz.

Bu notebook’u Colab’da çalıştırmak için buraya tıklayın.

Kod hücresi içeriğini gizle

from os.path import basename, exists


def download(url):
    filename = basename(url)
    if not exists(filename):
        from urllib.request import urlretrieve

        local, _ = urlretrieve(url, filename)
        print("İndirildi: " + local)


download("https://github.com/TerekliTahaBerk/thinkstatstr/raw/v3/nb/thinkstats.py")

Kod hücresi içeriğini gizle

try:
    import empiricaldist
except ImportError:
    %pip install empiricaldist

Kod hücresi içeriğini gizle

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from thinkstats import decorate

3.1. PMF’ler#

Pmf nesnesi, frekanslar yerine olasılıkları içeren bir FreqTab nesnesine benzer. Dolayısıyla Pmf oluşturmanın yollarından biri FreqTab ile başlamaktır. Örneğin aşağıdaki FreqTab, kısa bir dizideki değerlerin dağılımını temsil eder.

from empiricaldist import FreqTab

ftab = FreqTab.from_seq([1, 2, 2, 3, 5])
ftab
freqs
1 1
2 2
3 1
5 1

Frekansların toplamı, başlangıçtaki dizinin büyüklüğüdür.

n = ftab.sum()
n
np.int64(5)

Frekansları ne bölersek sayımlar yerine oranları temsil ederler.

pmf = ftab / n
pmf
probs
1 0.2
2 0.4
3 0.2
5 0.2

Bu sonuç, dizideki değerlerin %20’sinin 1, %40’ının 2 olduğunu ve bu şekilde devam ettiğini gösterir.

Bu oranları şu anlamda olasılık olarak da düşünebiliriz: Başlangıçtaki diziden rastgele bir değer seçersek 1’i seçme olasılığımız 0,2, 2’yi seçme olasılığımız 0,4’tür ve bu şekilde devam eder.

ne böldüğümüz için olasılıkların toplamı 1’dir; başka bir deyişle bu dağılım normalleştirilmiştir.

pmf.sum()
np.float64(1.0)

Normalleştirilmiş bir FreqTab nesnesi olasılık kütle fonksiyonunu (PMF) temsil eder. Bu adın kullanılmasının nedeni, ayrık değerlerle ilişkili olasılıklara “olasılık kütleleri” de denmesidir.

empiricaldist kütüphanesi olasılık kütle fonksiyonunu temsil eden bir Pmf nesnesi sağlar. Dolayısıyla önce FreqTab oluşturup sonra normalleştirmek yerine doğrudan bir Pmf nesnesi oluşturabiliriz.

from empiricaldist import Pmf

pmf = Pmf.from_seq([1, 2, 2, 3, 5])
pmf
probs
1 0.2
2 0.4
3 0.2
5 0.2

Toplam olasılığın 1 olması için Pmf normalleştirilmiştir.

pmf.sum()
np.float64(1.0)

Pmf ve FreqTab nesneleri birçok yönden birbirine benzer. Bir değerle ilişkili olasılığı aramak için köşeli ayraç işlecini kullanabiliriz.

pmf[2]
np.float64(0.4)

Ya da Pmfyi fonksiyon gibi çağırmak için parantez kullanabiliriz.

pmf(2)
np.float64(0.4)

Bir değere olasılık atamak için köşeli ayraç işlecini kullanmanız gerekir.

pmf[2] = 0.2
pmf(2)
np.float64(0.2)

Bir değerle ilişkili olasılığı artırarak mevcut bir Pmfyi değiştirebilirsiniz:

pmf[2] += 0.3
pmf[2]
np.float64(0.5)

Bir olasılığı belirli bir katsayıyla da çarpabilirsiniz:

pmf[2] *= 0.5
pmf[2]
np.float64(0.25)

Bir Pmfyi değiştirirseniz sonuç normalleştirilmiş olmayabilir; yani olasılıkların toplamı artık 1 etmeyebilir.

pmf.sum()
np.float64(0.8500000000000001)

normalize yöntemi Pmfyi toplama bölerek yeniden normalleştirir ve toplamı döndürür.

pmf.normalize()
np.float64(0.8500000000000001)

Pmf nesneleri, asıl nesneyi etkilemeden bir kopya oluşturup değiştirebilmeniz için copy yöntemini sunar.

pmf.copy()
probs
1 0.235294
2 0.294118
3 0.235294
5 0.235294

FreqTab gibi Pmf nesnesinin de niceliklere erişen qs ve olasılıklara erişen ps nitelikleri vardır.

Ayrıca Pmfyi çubuk grafik olarak çizen bar ve çizgi grafik olarak çizen plot yöntemlerini sunar.

3.2. Bir PMF’nin özetlenmesi#

1. Bölümde bir örneklemin ortalamasını öğeleri toplayıp öğe sayısına bölerek hesapladık. Basit bir örnek şöyledir.

seq = [1, 2, 2, 3, 5]

n = len(seq)
mean = np.sum(seq) / n
mean
np.float64(2.6)

Şimdi dizideki değerlerin PMF’sini hesapladığımızı düşünelim.

pmf = Pmf.from_seq(seq)

Pmf elimizdeyken ortalamayı yine hesaplayabiliriz; ancak işlem farklıdır. Olasılıklarla nicelikleri çarpıp çarpımları toplamamız gerekir.

mean = np.sum(pmf.ps * pmf.qs)
mean
np.float64(2.6)

Pmfyi normalleştirirken bu işlemi zaten yaptığımız için ne bölmemiz gerekmediğine dikkat edin. Pmf nesnelerinde aynı işlemi yapan bir mean yöntemi vardır.

pmf.mean()
np.float64(2.6)

Bir Pmf verildiğinde her niceliğin ortalamadan sapmasını hesaplayarak varyansı bulabiliriz.

deviations = pmf.qs - mean

Ardından sapmaların karelerini olasılıklarla çarpıp çarpımları toplarız.

var = np.sum(pmf.ps * deviations**2)
var
np.float64(1.84)

var yöntemi aynı işlemi yapar.

pmf.var()
np.float64(1.84)

Varyanstan standart sapmayı her zamanki gibi hesaplayabiliriz.

np.sqrt(var)
np.float64(1.3564659966250536)

Ya da aynı işlemi std yöntemiyle yapabiliriz.

pmf.std()
np.float64(1.3564659966250536)

Pmf ayrıca en yüksek olasılığa sahip değeri bulan bir mode yöntemi sunar.

pmf.mode()
np.int64(2)

İlerledikçe başka yöntemler de göreceğiz; ancak başlamak için bu kadarı yeterli.

3.3. Sınıf büyüklüğü paradoksu#

Pmf nesneleriyle neler yapabileceğimize örnek olarak “sınıf büyüklüğü paradoksu” adını verdiğim olguyu ele alalım.

ABD’deki pek çok yüksekokul ve üniversitede öğrenci-öğretim üyesi oranı yaklaşık 10:1’dir. Ancak öğrenciler derslerinin çoğunda 10’dan, bazen çok daha fazla sayıda öğrenci bulunmasına şaşırır. Bu tutarsızlığın iki nedeni vardır:

  • Öğrenciler genellikle dönem başına 4 veya 5 ders alırken öğretim üyeleri çoğunlukla 1 veya 2 ders verir.

  • Küçük bir sınıftaki öğrenci sayısı az, büyük bir sınıftaki öğrenci sayısıysa fazladır.

İlk etki, en azından belirtildiğinde açıktır; ikincisiyse daha inceliklidir. Bir örneğe bakalım. Bir yüksekokulun belirli bir dönemde 65 ders sunduğunu ve aşağıdaki büyüklük aralıklarının her birinde kaç ders bulunduğunu bildiğimizi varsayalım.

ranges = pd.interval_range(start=5, end=50, freq=5, closed="left")
ranges.name = "sınıf büyüklüğü"

data = pd.DataFrame(index=ranges)
data["count"] = [8, 8, 14, 4, 6, 12, 8, 3, 2]
data
count
sınıf büyüklüğü
[5, 10) 8
[10, 15) 8
[15, 20) 14
[20, 25) 4
[25, 30) 6
[30, 35) 12
[35, 40) 8
[40, 45) 3
[45, 50) 2

Pandas’ın interval_range fonksiyonu, her etiketi bir değer aralığını temsil eden bir Index oluşturur. [5, 10) gösterimi, 5in aralığa dâhil olduğunu, 10un ise olmadığını belirtir. Her aralıktaki sınıfların büyüklüklerini bilmediğimiz için bütün büyüklüklerin aralığın orta noktasında olduğunu varsayalım.

sizes = ranges.left + 2
sizes
Index([7, 12, 17, 22, 27, 32, 37, 42, 47], dtype='int64')

Şimdi sınıf büyüklüklerinin dağılımını temsil eden bir Pmf oluşturalım. Büyüklükleri ve frekanslarını bildiğimiz için sayımları, büyüklükleri ve bir adı argüman olarak geçirerek doğrudan Pmf oluşturabiliriz. Yeni Pmfyi normalleştirdiğimizde sonuç, sayıların toplamıdır.

counts = data["count"]
actual_pmf = Pmf(counts, sizes, name="gözlenen")
actual_pmf.normalize()
np.int64(65)

Yüksekokula ortalama sınıf büyüklüğünü sorarsanız bu dağılımın ortalaması olan 23,7 değerini bildirir.

actual_pmf.mean()
np.float64(23.692307692307693)

Ancak bir grup öğrenciye derslerinde kaç öğrenci bulunduğunu sorup ortalamayı hesaplarsanız sonuç daha büyük çıkar. Ne kadar büyük olduğuna bakalım.

Aşağıdaki fonksiyon sınıf büyüklüklerinin gerçek Pmfsini alır ve öğrencilerin gördüğü sınıf büyüklüklerini temsil eden yeni bir Pmf oluşturur. İki dağılımdaki nicelikler aynıdır; ancak dağılımdaki olasılıklar niceliklerle çarpılır. Çünkü büyüklüğü x olan bir sınıfı gözlemleyen x öğrenci vardır. Dolayısıyla bir sınıfı gözlemleme olasılığı, o sınıfın büyüklüğüyle orantılıdır.

def bias(pmf, name):
    # Her olasılığı sınıf büyüklüğüyle çarp
    ps = pmf.ps * pmf.qs

    # Yeni bir Pmf oluştur ve normalleştir
    new_pmf = Pmf(ps, pmf.qs, name=name)
    new_pmf.normalize()
    return new_pmf

Şimdi öğrencilerin gözlemlediği yanlı Pmfyi hesaplayabiliriz.

observed_pmf = bias(actual_pmf, name="gözlenen")

İki dağılım şu şekilde görünür.

from thinkstats import two_bar_plots

two_bar_plots(actual_pmf, observed_pmf, width=2)
decorate(xlabel="Sınıf büyüklüğü", ylabel="PMF")
_images/cdf786d0b4d3fe7a100d4cdc2517992dfb375ec74fffe3f5e01fcdf7f285696c.png

Gözlenen dağılımda küçük sınıflar daha az, büyük sınıflarsa daha fazladır. Yanlı ortalama 29,1’dir; bu değer gerçek ortalamadan yaklaşık %25 daha yüksektir.

observed_pmf.mean()
np.float64(29.123376623376622)

Bu işlemi tersine çevirmek de mümkündür. Bir yüksekokuldaki sınıf büyüklüklerinin dağılımını bulmak istediğinizi, ancak güvenilir veriye ulaşamadığınızı varsayalım. Seçeneklerden biri rastgele bir öğrenci örneklemi seçip derslerinde kaç öğrenci bulunduğunu sormaktır.

Az önce gördüğümüz nedenlerle sonuç yanlı olur; ancak gerçek dağılımı tahmin etmek için yine de kullanılabilir. Aşağıdaki fonksiyon olasılıkları sınıf büyüklüklerine bölerek bir Pmfdeki yanlılığı giderir.

def unbias(pmf, name):
    # Her olasılığı sınıf büyüklüğüne böl
    ps = pmf.ps / pmf.qs

    new_pmf = Pmf(ps, pmf.qs, name=name)
    new_pmf.normalize()
    return new_pmf

Sonuç şöyledir.

debiased_pmf = unbias(observed_pmf, "yanlılığı giderilmiş")
debiased_pmf.mean()
np.float64(23.692307692307693)

Yanlılığı giderilmiş Pmfnin ortalaması, başlangıçtaki gerçek dağılımın ortalamasıyla aynıdır.

Bu örnek ilginizi çektiyse bu ve “inceleme paradoksu” denen olgunun başka örneklerini içeren Probably Overthinking It kitabının 2. Bölümünü beğenebilirsiniz.

3.4. NSFG verileri#

Önceki bölümde ilk bebeklerle diğerlerinin gebelik sürelerine ait frekans tablolarını çizdik. Ancak grupların büyüklükleri aynı olmadığı için frekans tablolarını doğrudan karşılaştıramayız. PMF’ler normalleştirildiği için onları karşılaştırabiliriz. Öyleyse NSFG verilerini yeniden yükleyip gebelik süresi dağılımlarını temsil eden Pmf nesneleri oluşturalım.

Aşağıdaki hücreler veri dosyalarını indirir ve verileri okumak için ihtiyaç duyduğumuz statadict paketini kurar.

try:
    import statadict
except ImportError:
    %pip install statadict
download("https://github.com/TerekliTahaBerk/thinkstatstr/raw/v3/nb/nsfg.py")
download("https://github.com/TerekliTahaBerk/thinkstatstr/raw/v3/data/2002FemPreg.dct")
download("https://github.com/TerekliTahaBerk/thinkstatstr/raw/v3/data/2002FemPreg.dat.gz")

nsfg modülü verileri okuyan, canlı doğumları temsil eden satırları seçen ve bunları ilk bebeklerle diğerleri olarak ikiye ayıran get_nsfg_groups fonksiyonunu sunar. Fonksiyon üç DataFrame nesnesi döndürür.

from nsfg import get_nsfg_groups

live, firsts, others = get_nsfg_groups()

Her gruptaki gebelik süreleri için birer Pmf oluşturmak üzere firsts ve others nesnelerini kullanabiliriz.

first_pmf = Pmf.from_seq(firsts["prglngth"], name="ilkler")
other_pmf = Pmf.from_seq(others["prglngth"], name="diğerleri")

İlk bebeklerle diğerlerinin PMF’leri aşağıda çubuk grafik olarak gösterilmiştir.

two_bar_plots(first_pmf, other_pmf)
decorate(xlabel="Hafta", ylabel="Olasılık", xlim=[20, 50])
_images/d31d10076f0608f203adcc743cf21175149172c879ef950d8e5a517874cd882f.png

Frekans tablosu yerine PMF çizerek örneklem büyüklükleri arasındaki farkın bizi yanıltmasına izin vermeden iki dağılımı karşılaştırabiliriz. Bu şekle göre ilk bebeklerin zamanında (39. haftada) doğma olasılığı diğer bebeklerden daha düşük, geç doğma olasılığıysa (41. ve 42. haftalarda) daha yüksek görünüyor.

3.5. Diğer görselleştirmeler#

Frekans tabloları ve PMF’ler, verileri keşfederken ve örüntülerle ilişkileri belirlemeye çalışırken kullanışlıdır. Neler olup bittiğine dair bir fikriniz oluştuğunda iyi bir sonraki adım, belirlediğiniz örüntüleri olabildiğince açık gösteren bir görselleştirme tasarlamaktır.

NSFG verilerinde dağılımlar arasındaki en büyük farklar modun yakınındadır. Bu nedenle grafiğin o bölümüne yakınlaşıp 35 ile 46. haftalar arasındaki verileri seçmek mantıklıdır.

Bir Pmf nesnesini fonksiyon gibi çağırdığımızda bir nicelik dizisini arayıp karşılık gelen olasılık dizisini elde edebiliriz.

weeks = range(35, 46)
first_pmf(weeks)
array([0.03602991, 0.03897575, 0.04713347, 0.06163608, 0.4790392 ,
       0.12145932, 0.08157716, 0.04645366, 0.01971448, 0.00521187,
       0.00135962])
other_pmf(weeks)
array([0.03210137, 0.03146779, 0.05216473, 0.07074974, 0.54466737,
       0.12249208, 0.04794087, 0.02597677, 0.01288279, 0.00485744,
       0.00084477])

Böylece olasılıklar arasındaki farkları şu şekilde hesaplayabiliriz.

diffs = first_pmf(weeks) - other_pmf(weeks)
diffs
array([ 0.00392854,  0.00750796, -0.00503126, -0.00911366, -0.06562817,
       -0.00103276,  0.03363629,  0.02047689,  0.00683169,  0.00035443,
        0.00051485])

Farkları yüzde puan olarak ifade etmek için 100 ile çarptığımızda şu şekilde görünürler.

plt.bar(weeks, diffs * 100)
decorate(xlabel="Hafta", ylabel="Fark (yüzde puan)")
_images/564466c8eef3b35c970d0bb934d3f6712e99b1ec98988717cb854169cddf24d3.png

Bu şekil örüntüyü daha açık gösteriyor: İlk bebeklerin 39. haftada doğma olasılığı daha düşük, 41. ve 42. haftalarda doğma olasılığıysa biraz daha yüksektir.

Bir örneklemde böyle bir örüntü gördüğümüzde bunun evrende de geçerli olduğundan emin olamayız; aynı evrenden alınan başka bir örneklemde örüntüyü yeniden görüp görmeyeceğimizi de bilemeyiz. Bu soruya 9. Bölümde yeniden döneceğiz.

3.6. Terimler#

Bu bölümde önceki bölümlerdeki kadar çok yeni terim yoktur.

  • normalleştirilmiş: Bir olasılık kümesinin toplamı 1 ise bu küme normalleştirilmiştir.

  • olasılık kütle fonksiyonu (PMF): Her niceliği olasılığıyla eşleyerek bir dağılımı temsil eden fonksiyon.

3.7. Alıştırmalar#

Bu bölümün alıştırmalarında her katılımcı için bir satır içeren NSFG katılımcı dosyasını kullanacağız. Verileri indirme talimatları bu bölümün notebook’unda yer alıyor.

download("https://github.com/TerekliTahaBerk/thinkstatstr/raw/v3/data/2002FemResp.dct")
download("https://github.com/TerekliTahaBerk/thinkstatstr/raw/v3/data/2002FemResp.dat.gz")

Bu veri kümesinin kod kitabına https://ftp.cdc.gov/pub/Health_Statistics/NCHS/Dataset_Documentation/NSFG/Cycle6Codebook-Female.pdf adresinden ulaşabilirsiniz.

nsfg.py modülü katılımcı dosyasını okuyup bir DataFrame döndüren bir fonksiyon sağlar.

from nsfg import read_fem_resp

resp = read_fem_resp()
resp.shape
(7643, 3092)

Bu DataFrame 7.643 satır ve 3.092 sütun içerir.

3.7.1. Alıştırma 3.1#

Her katılımcının “canlı doğan bebek sayısını” kaydeden numbabes sütununu seçin. Bir FreqTab nesnesi oluşturup bu sütundaki değerlerin frekanslarını görüntüleyin. Bunların kod kitabındaki frekanslarla tutarlı olduğunu kontrol edin. NaN ile değiştirilmesi gereken özel değerler var mı?

Ardından bir Pmf nesnesi oluşturup çubuk grafik olarak çizin. Dağılım simetrik mi, sola mı çarpık, yoksa sağa mı çarpık?

pmf.bar()
decorate(xlabel="Bebek sayısı", ylabel="PMF")
_images/c98680eb90de97bf9349c2f23f9bacc40684c3d8fb1beeb561df210cb016fea9.png

3.7.2. Alıştırma 3.2#

Ortalamanın dağılımdaki merkezî bir noktayı belirlemesine ve varyansın yayılımı nicelleştirmesine benzer biçimde, dağılımın sola mı sağa mı çarpık olduğunu gösteren çarpıklık adlı başka bir istatistik vardır.

Bir örneklemin çarpıklığını, sapmaların küplerinin toplamını standart sapmanın küpüne bölerek hesaplayabiliriz. Örneğin numbabes değişkeninin çarpıklığını şu şekilde hesaplarız.

numbabes = resp["numbabes"].replace(97, np.nan)
deviations = numbabes - numbabes.mean()
skewness = np.mean(deviations**3) / numbabes.std(ddof=0) ** 3
skewness
np.float64(1.7018914266755958)

Pozitif değer, dağılımın sağa; negatif değerse sola çarpık olduğunu gösterir.

Bir değer dizisi yerine Pmf verilirse çarpıklığı şöyle hesaplayabilirsiniz:

  1. Pmf içindeki her niceliğin ortalamadan sapmasını hesaplayın.

  2. Sapmaların küpünü alın, Pmf içindeki olasılıklarla çarpın ve çarpımları toplayın.

  3. Toplamı standart sapmanın küpüne bölün.

Bir Pmf nesnesini alıp çarpıklığını döndüren pmf_skewness adlı bir fonksiyon yazın.

Fonksiyonunuzu ve numbabes değişkeninin Pmfsini kullanarak çarpıklığı hesaplayın; yukarıda hesapladığımız sonuçla aynı değeri elde ettiğinizi doğrulayın.

3.7.3. Alıştırma 3.3#

Çocuklarla anket yapıp ailelerinde kaç çocuk olduğunu sorarsanız sınıf büyüklüğü paradoksuna benzer bir durum ortaya çıkar. Çok çocuklu ailelerin örnekleminizde yer alma olasılığı daha yüksektir; çocuksuz ailelerinse örneklemde yer alma olasılığı yoktur.

resp içinden her katılımcının hanesindeki 18 yaşından küçük çocuk sayısını kaydeden numkdhh sütununu seçin. Bu sütundaki değerlerden bir Pmf oluşturun.

Çocuklarla anket yapıp hanelerinde kendileri dâhil 18 yaşından küçük kaç çocuk olduğunu sorsaydık göreceğimiz dağılımı hesaplamak için bias fonksiyonunu kullanın.

Gerçek ve yanlı dağılımları çizip ortalamalarını hesaplayın.

Orijinal eser: Think Stats: Exploratory Data Analysis in Python, 3rd Edition

Telif hakkı 2024 Allen B. Downey

Türkçe çeviri: ThinkStatsTr

Kod lisansı: MIT License

Metin lisansı: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International