Veri Biliminde Python'ın Sık Kullanılan Söz Dizimi (İleri Seviye)

Son günlerde Data Science from Scratch (PDF adresi) kitabına bakıyordum. Veri bilimine giriş için oldukça anlaşılır ve iyi bir kaynak. Kitaptaki bir bölüm Python’ın temel ve veri biliminde sık kullanılan ileri seviye söz dizimini oldukça sade ve net bir şekilde anlatıyordu. Bu anlatımı çok beğendiğim için, kendime not olması amacıyla buraya çeviriyorum.
Veri Biliminde Sık Kullanılan Python Söz Dizimi (Temel)
Veri Biliminde Sık Kullanılan Python Söz Dizimi (İleri Seviye)

Bu bölüm, veri işlemede oldukça faydalı olan Python’ın ileri seviye söz dizimi ve özelliklerini (Python 2.7 tabanlı) tanıtmaya odaklanmaktadır.

Sıralama Sorting

Python listelerini sıralamak istiyorsanız, listenin sort metodunu kullanabilirsiniz. Orijinal listeyi bozmak istemiyorsanız, sorted fonksiyonunu kullanarak sıralanmış yeni bir liste döndürebilirsiniz:

x = [4,1,2,3]
y = sorted(x)       # y = [1,2,3,4], x değişmez
x.sort()            # şu anki x = [1,2,3,4]
sort veya sorted varsayılan olarak listeleri küçükten büyüğe doğru sıralar.

Büyükten küçüğe sıralamak isterseniz, reverse = True parametresini belirtebilirsiniz.

Ayrıca, listeyi belirli bir anahtara göre sıralamak için kendi sıralama fonksiyonunuzu da tanımlayabilirsiniz:

# Mutlak değerine göre büyükten küçüğe sırala
x = sorted([-4,1,-2,3], key=abs, reverse=True) # is [-4,3,-2,1]
# Kelimelerin tekrar sayısına göre büyükten küçüğe sırala
wc = sorted(word_counts.items(),
key=lambda (word, count): count,
reverse=True)

Liste Anlayışları List Comprehensions

Sık sık bir listeden belirli öğeleri çıkarıp yeni bir liste oluşturmak, bazı öğelerin değerlerini değiştirmek veya her ikisini birden yapmak isteyebiliriz. Python’daki yaygın yöntem, Liste Anlayışları (List Comprehensions) kullanmaktır:

even_numbers = [x for x in range(5) if x % 2 == 0]  # [0, 2, 4]
squares = [x * x for x in range(5)]                 # [0, 1, 4, 9, 16]
even_squares = [x * x for x in even_numbers]        # [0, 4, 16]

Benzer şekilde, listeleri sözlüklere veya kümelere dönüştürebilirsiniz:

square_dict = { x : x * x for x in range(5) }       # { 0:0, 1:1, 2:4, 3:9, 4:16 }
square_set = { x * x for x in [1, -1] }             # { 1 }

Listedeki öğeleri kullanmanız gerekmiyorsa, değişken olarak alt çizgi (_) kullanabilirsiniz:

zeroes = [0 for _ in even_numbers] # even_numbers listesiyle aynı uzunlukta

Liste anlayışları çoklu for döngülerini destekler:

pairs = [(x, y)
    for x in range(10)
    for y in range(10)]    # Toplam 100 çift: (0,0) (0,1) ... (9,8), (9,9)

Sonraki for döngüleri, önceki for döngülerinin sonuçlarını kullanabilir:

increasing_pairs = [(x, y)                      # Sadece x < y olan veri çiftlerini içerir
                    for x in range(10)          # range(lo, hi) eşittir
                    for y in range(x + 1, 10)]  # [lo, lo + 1, ..., hi - 1]

Liste anlayışlarını ileride sıkça kullanacağız.

Üreteçler ve Yineleyiciler Generators and Iterators

Listelerin bir sorunu, dikkatsizce kullanıldığında çok büyük hale gelebilmeleridir; örneğin, range(1000000) bir milyon öğeli bir liste oluşturur. Verileri birer birer işlemek çok zaman alabilir (veya belleği tüketebilir). Oysa çoğu zaman sadece ilk birkaç veriye ihtiyacınız olabilir, bu durumda diğer işlemler gereksizdir.

Üreteçler ise yalnızca ihtiyacınız olan verileri yinelemenizi sağlar. Bir üreteç oluşturmak için fonksiyonları ve yield ifadesini kullanabilirsiniz:

def lazy_range(n):
    """range'in tembel bir versiyonu"""
    i = 0
    while i < n:
        yield i
        i += 1

Çevirmenin notu: Üreteçler de özel bir tür yineleyicidir. yield anahtar kelimesi, üretecin yinelemeyi gerçekleştirmesinin anahtarıdır. Bir üretecin yürütmesini duraklatıp devam ettirme noktası olarak işlev görür; yield ifadesine bir değer atanabilir veya yield ifadesinin değeri döndürülebilir. yield ifadesi içeren herhangi bir fonksiyona üreteç denir. Bir üreteçten çıkıldığında, üreteç mevcut yürütme durumunu kaydeder ve bir sonraki yineleme değerini elde etmek için bir sonraki çağrıda kaldığı yerden devam eder. Liste yinelemesi büyük bellek alanı kaplarken, üreteç kullanımı neredeyse tek bir adres alanı kaplayarak bellek tasarrufu sağlar.

Aşağıdaki döngü, yield’deki değerleri teker teker, hepsi bitene kadar tüketecektir:

for i in lazy_range(10):
    do_something_with(i)

(Aslında Python’ın yukarıdaki _lazy_range_ etkisini gerçekleştiren kendi xrange fonksiyonu vardır; Python 3’te ise range varsayılan olarak tembeldir.) Bu, sonsuz bir dizi oluşturabileceğiniz anlamına gelir:

def natural_numbers():
    """1, 2, 3, ... döndürür"""
    n = 1
    while True:
        yield n
        n += 1

Ancak, döngüden çıkış mantığı olmayan bu tür ifadelerin kullanılması tavsiye edilmez.

İPUCU

Üreteçlerle yinelemenin bir dezavantajı, öğelerin baştan sona yalnızca bir kez yinelenebilmesidir. Birden fazla yineleme yapmak isterseniz, her seferinde yeni bir üreteç oluşturmanız veya bir liste kullanmanız gerekir.

İkinci bir üreteç oluşturma yöntemi: Parantez içindeki anlama ifadelerini kullanmak:

lazy_evens_below_20 = (i for i in lazy_range(20) if i % 2 == 0)

Sözlüklerdeki items() metodunun sözlükteki tüm anahtar-değer çiftlerinin bir listesini döndürdüğünü biliyoruz, ancak çoğu zaman iteritems() üreteç metodunu kullanarak her seferinde yalnızca bir anahtar-değer çifti üretip döndürerek yineleme yaparız.

Rastgelelik Randomness

Veri bilimi öğrenirken sık sık rastgele sayılar üretmemiz gerekecek, bu yüzden sadece random modülünü içe aktararak kullanabiliriz:

import random
four_uniform_randoms = [random.random() for _ in range(4)]
# [0.8444218515250481,        # random.random() rastgele sayı üretir
# 0.7579544029403025,         # Rastgele sayı 0 ile 1 arasında normalize edilmiştir
# 0.420571580830845,          # Bu fonksiyon, rastgele sayı üretmek için en sık kullanılan fonksiyondur
# 0.25891675029296335]

Tekrarlanabilir sonuçlar elde etmek isterseniz, random modülünü random.seed ile ayarlanan dahili duruma göre sözde rastgele (yani deterministik) sayılar üretmesini sağlayabilirsiniz:

random.seed(10)           # seed'i 10 olarak ayarla
print random.random()     # 0.57140259469
random.seed(10)           # seed'i tekrar 10 olarak ayarla
print random.random()     # 0.57140259469 tekrar

Bazen belirli bir aralıktaki rastgele bir sayı üretmek için random.randrange fonksiyonunu da kullanırız:

random.randrange(10)      # range(10) = [0, 1, ..., 9] içinden rastgele bir sayı seçer
random.randrange(3, 6)    # range(3, 6) = [3, 4, 5] içinden rastgele bir sayı seçer

Bazen kullanımı çok pratik olan başka yöntemler de vardır; örneğin, random.shuffle bir listedeki öğelerin sırasını karıştırarak rastgele sıralanmış bir liste oluşturur:

up_to_ten = range(10)
random.shuffle(up_to_ten)
print up_to_ten
# [2, 5, 1, 9, 7, 3, 8, 6, 4, 0] (Sizin sonucunuz farklı olabilir)

Bir listeden rastgele bir öğe seçmek isterseniz, random.choice metodunu kullanabilirsiniz:

my_best_friend = random.choice(["Alice", "Bob", "Charlie"]) # Bana "Bob" çıktı

Hem rastgele bir dizi oluşturmak hem de orijinal listeyi bozmak istemiyorsanız, random.sample metodunu kullanabilirsiniz:

lottery_numbers = range(60)
winning_numbers = random.sample(lottery_numbers, 6) # [16, 36, 10, 6, 25, 9]

Birden fazla rastgele örnek seçimi (tekrarlara izin verilir) birden çok çağrı yaparak gerçekleştirebilirsiniz:

four_with_replacement = [random.choice(range(10))
                         for _ in range(4)]
# [9, 4, 4, 2]

Düzenli İfadeler Regular Expressions

Düzenli ifadeler metin aramada kullanılır; biraz karmaşık olsalar da çok faydalıdırlar ve bu konuda birçok özel kitap bulunmaktadır. Onlarla karşılaştığımızda daha detaylı açıklayacağız, aşağıda Python’da düzenli ifadelerin kullanımına dair bazı örnekler verilmiştir:

import re
print all([                                 # Aşağıdaki ifadelerin hepsi true döner, çünkü
    not re.match("a", "cat"),               # * 'cat' 'a' ile başlamaz
    re.search("a", "cat"),                  # * 'cat' kelimesi 'a' harfini içerir
    not re.search("c", "dog"),              # * 'dog' kelimesi 'c' harfini içermez
    3 == len(re.split("[ab]", "carbs")),    # * 'a' veya 'b'ye göre kelimeyi üç parçaya ayırır ['c','r','s']
    "R-D-" == re.sub("[0-9]", "-", "R2D2")  # * Sayıları tire ile değiştirir
    ])                                      # Çıktı True

Nesne Yönelimli Programlama Object-Oriented Programming

Birçok dilde olduğu gibi, Python da verileri kapsülleyen sınıflar ve bu veriler üzerinde işlem yapan fonksiyonlar tanımlamanıza olanak tanır. Kodumuzu daha anlaşılır ve düzenli hale getirmek için bazen bunları kullanırız. Bol yorumlu bir örnekle açıklamak en basit yol olabilir. Python’ın yerleşik küme (Set) yapısı olmadığını varsayarsak, kendi Set sınıfımızı oluşturmak isteyebiliriz. Peki bu sınıf hangi özelliklere sahip olmalı? Örneğin, belirli bir Set verildiğinde, ona öğe ekleyebilmeli, öğe silebilmeli ve belirli bir değeri içerip içermediğini kontrol edebilmeliyiz. Bu nedenle, tüm bu işlevleri sınıfın üye fonksiyonları olarak oluşturacağız. Böylece, Set nesnesinden sonra nokta kullanarak bu üye fonksiyonlara erişebiliriz:

# Geleneksel olarak, sınıf isimlerini _PascalCase_ olarak veririz
class Set:
    # Bunlar üye fonksiyonlarıdır
    # Her üye fonksiyonunun ilk parametresi "self"tir (başka bir gelenek)
    # "self", kullanılmakta olan belirli Set nesnesine karşılık gelir

    def __init__(self, values=None):
        """Bu, bir oluşturma fonksiyonudur
        Her yeni Set oluşturduğunuzda bu fonksiyon çağrılır
        Şu şekilde çağrılabilir
        s1 = Set() # Boş küme
        s2 = Set([1,2,2,3]) # Belirtilen değerlerle kümeyi başlatır"""
        self.dict = {} # Set'in her örneğinin kendi dict özelliği vardır
        # Bu özelliği her üyeyi takip etmek için kullanırız
        if values is not None:
            for value in values:
            self.add(value)

    def __repr__(self):
        """Bu, Set nesnesinin string ifadesidir
        Python komut penceresine string yazarak veya str() metodunu kullanarak nesneye string iletebilirsiniz"""
        return "Set: " + str(self.dict.keys())

    # self.dict içindeki bir anahtar olarak ve anahtarın değerini True yaparak üyeliği temsil edeceğiz
    def add(self, value):
        self.dict[value] = True

    # Eğer parametre sözlükteki bir anahtar ise, ilgili değer Set'in içindedir
    def contains(self, value):
        return value in self.dict

    def remove(self, value):
        del self.dict[value]

Ardından Set’i şu şekilde kullanabiliriz:

s = Set([1,2,3])
s.add(4)
print s.contains(4)     # True
s.remove(3)
print s.contains(3)     # False

Fonksiyonel Araçlar Functional Tools

Kısmi Fonksiyonlar partial

Fonksiyonları iletirken, bazen bir fonksiyonun kısmi işlevselliğini kullanarak yeni bir fonksiyon oluşturmak isteyebiliriz. Basit bir örnekle, iki değişkenli bir fonksiyonumuz olduğunu varsayalım:

def exp(base, power):
    return base ** power

Bunu kullanarak, tek bir değişken alan ve tabanı 2 olan bir kuvvet fonksiyonunun exp(2, power) sonucunu döndüren bir fonksiyon oluşturmak istiyoruz.

Elbette, def ile yeni bir fonksiyon tanımlayabiliriz, ancak bu pek akıllıca görünmeyebilir:

def two_to_the(power):
  return exp(2, power)

Daha akıllıca bir yaklaşım functools.partial metodunu kullanmaktır:

from functools import partial
two_to_the = partial(exp, 2)      # Şu anki fonksiyonun sadece bir değişkeni var
print two_to_the(3)               # 8

Eğer isim belirtilirse, partial metodu diğer parametreleri de doldurmak için kullanılabilir:

square_of = partial(exp, power=2)
print square_of(3)                # 9

Fonksiyonun ortasında parametrelerle oynamaya çalışırsanız, program hızla karmaşık hale gelecektir, bu yüzden bu tür davranışlardan kaçınmaya çalışın.

Eşleme map

Bazen liste anlayışlarının işlevsel bir alternatifi olarak map, reduce ve filter gibi fonksiyonları da kullanırız:

def double(x):
    return 2 * x

xs = [1, 2, 3, 4]
twice_xs = [double(x) for x in xs]      # [2, 4, 6, 8]
twice_xs = map(double, xs)              # Aynı şekilde
list_doubler = partial(map, double)     # Fonksiyonun amacı listeyi ikiye katlamak
twice_xs = list_doubler(xs)             # Bu da [2, 4, 6, 8]

map metodu ayrıca çok parametreli fonksiyonların birden fazla listeye eşlenmesi için de kullanılabilir:

def multiply(x, y): return x * y

products = map(multiply, [1, 2], [4, 5])  # [1 * 4, 2 * 5] = [4, 10]

Filtreleme filter

Benzer şekilde, filter listelerdeki if işlevini gerçekleştirir:

def is_even(x):
    """x çift ise True, tek ise False döndürür"""
    return x % 2 == 0

x_evens = [x for x in xs if is_even(x)]   # [2, 4]
x_evens = filter(is_even, xs)             # Aynı şekilde
list_evener = partial(filter, is_even)    # Bu fonksiyon filtreleme işlevini gerçekleştirir
x_evens = list_evener(xs)                 # Bu da [2, 4]

İndirgeme reduce

reduce metodu, listedeki ilk ve ikinci öğeyi sürekli olarak birleştirir, ardından sonucu üçüncü öğeyle birleştirir ve bu süreci tek bir sonuç elde edene kadar tekrarlar:

x_product = reduce(multiply, xs)          # = 1 * 2 * 3 * 4 = 24
list_product = partial(reduce, multiply)  # Bu fonksiyon bir listeyi indirgeme işlevini gerçekleştirir
x_product = list_product(xs)              # Bu da 24

Numaralandırma enumerate

Bazen bir liste üzerinde dönerken hem öğeyi hem de indeksini aynı anda kullanmamız gereken durumlar ortaya çıkar:

# Pek Pythonik değil (pek zarif ve özlü değil)
for i in range(len(documents)):
    document = documents[i]
    do_something(i, document)

# Bu da pek Pythonik değil (pek zarif ve özlü değil)
i = 0
for document in documents:
    do_something(i, document)
    i += 1

En özlü yaklaşım, enumerate numaralandırma metodunu kullanarak (index, element) çiftleri üretmektir:

for i, document in enumerate(documents):
    do_something(i, document)

Benzer şekilde, sadece indeksi kullanmak isterseniz:

for i in range(len(documents)): do_something(i)   # Özlü değil
for i, _ in enumerate(documents): do_something(i) # Özlü

Bu metodu ileride sıkça kullanacağız.

Sıkıştırma ve Argüman Açma zip and Argument Unpacking

Sıkıştırma zip

Sık sık iki veya daha fazla listeyi sıkıştırma işlemi yaparız. Sıkıştırma, aslında birden çok listeyi karşılık gelen demetlerin tek bir listesi haline getirmektir:

list1 = ['a', 'b', 'c']
list2 = [1, 2, 3]
zip(list1, list2)       # [('a', 1), ('b', 2), ('c', 3)] elde edilir

Argüman Açma Argument Unpacking

Birden fazla listenin uzunluğu tutarsızsa, sıkıştırma işlemi en kısa listenin sonunda durur. Listeleri açmak için ilginç bir “unzip” (açma) hilesini de kullanabilirsiniz:

pairs = [('a', 1), ('b', 2), ('c', 3)]
letters, numbers = zip(*pairs)

Buradaki yıldız işareti, pairs’in öğelerini zip’in ayrı ayrı argümanları olarak kullanarak argüman açma işlemini gerçekleştirir. Aşağıdaki çağrı aynı etkiyi yaratır:

zip(('a', 1), ('b', 2), ('c', 3))  # [('a','b','c'), ('1','2','3')] döndürür

Argüman açma, diğer fonksiyonlarla da birlikte kullanılabilir:

def add(a, b): return a + b

add(1, 2)           # 3 döndürür
add([1, 2])         # Hata verir
add(*[1, 2])        # 3 döndürür

Pek pratik olmasa da, kodu daha özlü hale getirmek için güzel bir yöntemdir.

Değişken Uzunlukta Argüman Geçirme args and kwargs

Varsayalım ki, eski bir fonksiyonu girdi olarak alan ve bu eski fonksiyonun sonucunu 2 ile çarpan yeni bir fonksiyon döndüren bir üst düzey fonksiyon oluşturmak istiyoruz:

def doubler(f):
    def g(x):
      return 2 * f(x)
    return g

Örnek çalıştırma:

def f1(x):
    return x + 1

g = doubler(f1)
print g(3)        # 8 (== ( 3 + 1) * 2)
print g(-1)       # 0 (== (-1 + 1) * 2)

Ancak, birden fazla argüman geçtiğimizde bu yöntem pek işe yaramaz:

def f2(x, y):
    return x + y

g = doubler(f2)
print g(1, 2) # Hata TypeError: g() takes exactly 1 argument (2 given)

Bu yüzden, rastgele sayıda argümanı kabul edebilen bir fonksiyon belirtmemiz ve ardından argüman açma yoluyla birden fazla argümanı iletmemiz gerekiyor; bu biraz sihirli görünebilir:

def magic(*args, **kwargs):
    print "unnamed args:", args
    print "keyword args:", kwargs
magic(1, 2, key="word", key2="word2")
# Çıktı:
# unnamed args: (1, 2)
# keyword args: {'key2': 'word2', 'key': 'word'}

Bir fonksiyonu bu şekilde tanımladığımızda, args (arguments’ın kısaltması) isimsiz argümanları içeren bir demet (tuple) iken, kwargs (keyword arguments’ın kısaltması) isimli argümanları içeren bir sözlüktür (dictionary).

Bunlar, geçirilen parametrelerin bir liste (veya demet) veya dizi olduğu durumlarda da kullanılabilir:

def other_way_magic(x, y, z):
    return x + y + z

x_y_list = [1, 2]
z_dict = { "z" : 3 }
print other_way_magic(*x_y_list, **z_dict)    # 6

Bunu çeşitli garip yöntemlerle kullanabilirsiniz, ancak biz sadece üst düzey fonksiyonlara değişken uzunlukta argümanlar geçirme sorununu çözmek için kullanacağız:

def doubler_correct(f):
    """f ne olursa olsun etkili çalışır"""
    def g(*args, **kwargs):
        """Kaç parametre olursa olsun, bu fonksiyon parametreleri f'ye doğru şekilde iletir"""
        return 2 * f(*args, **kwargs)
    return g

g = doubler_correct(f2)
print g(1, 2) # 6

Veri Bilimi Dünyasına Hoş Geldiniz!

Ding! Tebrikler, yeni bir dünyanın kapılarını araladınız! Şimdi keyifli bir şekilde keşfe çıkabilirsiniz~

İlgili Okuma:

Veri Biliminde Sık Kullanılan Python Söz Dizimi (Temel)