AST415 Astronomide Sayısal Çözümleme - I

Ders - 05 Diziler ve Matrisler

Doç. Dr. Özgür Baştürk
Ankara Üniversitesi, Astronomi ve Uzay Bilimleri Bölümü
obasturk at ankara.edu.tr
http://ozgur.astrotux.org

Python ve NumPy İle Nümerik Diziler

Dizilere Neden İhtiyaç Duyulur?

Tanımlı bir $f(x)$ fonksiyonumuz olsun ve bu fonksiyonu $x_0$, $x_1$, $x_2$, … , $x_{n-1}$ şeklinde verilen $n$ tane $x$ değerine uygulamak istiyor olalım. Bütün $x_i$ bağımsız değişkenlerini bir listede, fonksiyondan dönecek değerlerle oluşacak $y_i = f(x_i)$ bağlı değişkenlerini ise başka bir listede toplayabiliriz. Aşağıda bu şekilde hazırlanmış bir örnek görüyorsunuz.

In [ ]:
def f(x):
    return x**3 # ornek fonksiyon

n = 5 # nokta sayisi
dx = 1.0 / (n-1) # [0,1] araligindaki x'lerin arasindaki uzaklik
xlist = [i*dx for i in range(n)]
ylist = [f(x) for x in xlist]
xy_ikili = [(x,y) for x,y in zip(xlist,ylist)] 
print("x listesi: ",xlist)
print("f(x) = y listesi: ",ylist)
print("(x,y) listesi: ", xy_ikili)

Listeler oldukça kullanışlı olmakla birlikte özellikle matematiksel işlemler için dizileri (matrisleri) kullanmanın a) bir fonksiyonu tüm liste elemanlarına tek bir kerede uygulamak, b) işlemleri hızlı gerçekleştirmek, c) tüm dizinin aynı tür elemanlardan oluşması gibi pek çok avantajı vardır. Listeler özellikle farklı türden elemanları tutabilmeleri nedeniyle esnek yapıdadırlar, ancak biraz yavaştırlar ve daha çok kullanıcıdan girdi verisi alırken, birden fazla tür nesne üzerinde işlem yapmak için kullanılırlar.

Python'da nümerik diziler liste elemanlarına benzemekle birlikte onlardan aşağıdaki özellikleri nedeniyle ayrılırlar.

NumPy Modülü

Python dilini bilimsel problemlerin çözümünde kullanırken fonksiyonlarına en sık başvurulan dış modüllerden biri $NumPy$ modülüdür (Bir diğeri de $SciPy$ modülüdür). Sağladığı dizi (ing. array) nesnesi ve bu nesne üzerinde tanımlı fonksiyonları sayesinde pek çok bilimsel problemin çözümüne büyük kolaylıklar getirir.

1) Dizinin tüm elemanları aynı türde olmalıdır. Bu tür nümerik işlemleri hızlı gerçekleştirmek ve sonuçlarını saklayabilmek açısından tam, reel, ya da kompleks sayı türlerinden biri olmalıdır.

2) Dizi oluşturulmadan önce eleman sayısı bilinmelidir.

3) Diziler (ing. arrays) standard Python dilinin bir parçası değil, NumPy modülünün bir parçasıdır. Dolayısı ile bu modül kurulmuş ve kullanılmadan önce import edilmiş olmalıdır.

4) NumPy modülü matematiksel işlemlerin döngü yapılarına gerek kalmaksızın tüm bir dizi üzerinde uygulanmasını sağlar. Bu yapı “vektörleştirme” (ing. vectorization) olarak bilinir ve Python programlarının döngü yapıları kullanılarak kodlanan programlara oranla önemli ölçüde farklı olmasını sağlar.

5) Tek indeksli diziler vektörler, iki ya da daha fazla indeksli diziler ise matrsiler ya da tablolar olarak adlandırılırlar.

Bir NumPy dizisi (array) aşağıdaki şekilllerde tanımlanabilir:

In [ ]:
import numpy as np # numpy modulunun np lakabiyla import edilmesi
n = 25 # dizi elemani sayisi
r = range(n) # [0,n] arasindaki tam sayilari iceren bir range nesnesi
a1 = np.array(r) # r range nesnesinin bir numpy dizisine donusturulmesi
a2 = np.zeros(n) # sadece 0 (sifir)'lardan olusan n elemanli dizi
a3 = np.ones(n) # sadece 1 (bir)'lerden olusan n elemanli dizi
p = 1; q = 5; n = 5 # baslangic (p), son (q) ve elaman sayisi (n)
a4 = np.linspace(p,q,n) # p ve q arasinda n tane eleman iceren dizi
print("linspace ile olusturulan a4 dizisi: ", a4)
# p ve q arasinda (p-q)/n aralikli n tane eleman iceren dizi
a5 = np.arange(p,q+1,(q - p) / (n - 1)) 
print("arange ile olusturulan a4 dizisi: ", a5)
# logaritmik artisla da dizi olusturulabilir.
a6 = np.logspace(p,q,n) # p ve q arasinda n tane eleman iceren dizi
print("logspace ile olusturulan a6 dizisi: ", a6)
In [ ]:
print(a4)

Eş Aralıklı Dizi Oluşturma Fonksiyonları

Eşit aralıklarla sıralanmış sayılarla $NumPy$ nümerik dizisi oluşturmanın bir yolu $arange(bas,son,adim)$ fonksiyonunu kullanmaktır. $arange$ fonksiyonu tıpkı $range$ fonksiyonu gibi çalışır. Farkı sonucun bir liste olması yerine bir $NumPy$ nümerik dizisi olması ve reel sayıları da içerebilmesidir. Argümanları dizinin başlangıç değeri ($bas$), diziden hariç tutulan son değer ($son$) ve adım değeridir ($adim$).

In [ ]:
import numpy as np
a = np.arange(1.,-1.,-0.5)
print("a = ", a)

Aynı işlem daha önce de gördüğünüz $linspace$ fonksiyonu kullanarak da yapılabilir. $linspace(bas,son,sayi)$ fonksiyonunun argümanları ise başlangıç değeri ($bas$), diziye dahil edilecek son değer ($son$) ve kaç tane dizi elemanı ($s$) istendiğidir.

In [ ]:
b = np.linspace(-1, 0.5, 4)
print("b = ", b)

NumPy modülünde arange fonksiyonu ve linspace fonksiyonuna özdeş ancak oldukça kompakt bir ifade daha bulunmaktadır: $r\_[f:t:s]$

In [2]:
import numpy as np
a = np.r_[-5:5:11j] # linspace(-5, 5, 11) yazimina ozdes
print("a = np.r_[-5:5:11j] -->", a)
b = np.r_[-5:5:1.0] # arange(-5.,5.,1.) yazimina ozdes
print("b = np.r_[-5:5:1.0] -->", b)
a = np.r_[-5:5:11j] --> [-5. -4. -3. -2. -1.  0.  1.  2.  3.  4.  5.]
b = np.r_[-5:5:1.0] --> [-5. -4. -3. -2. -1.  0.  1.  2.  3.  4.]

Nümerik Dizilerde İndeksleme ve Dilimleme İşlemleri 1

Python'da nümerik dizilerde indeksleme ve dilimleme çok önemli bir fark dışında listelerdekiyle aynı şekilde yapılır. Bu çok önemli fark dilimlerin orjinal dizinin bir kopyası olmayıp gerçekten bir bölümü olmasıdır. Dolayısı ile dilim üzerinde bir değişiklik yapıldığı vakit orjinal dizide de bu dilimin karşılık geldiği elemanlar değişir!

In [ ]:
import numpy as np  # numpy modulunun np lakabiyla import edilmesi
a = np.linspace(1,10,10)
print("a: ", a)
# linspace reel sayilardan (float) mutesekkil bir dizi yaratir
print("a[0]: {:.2f}, a[-1]: {:.2f}, a[5]: {:.2f}".format(a[0],a[-1],a[5]))
print("a[:5]: ", a[:5])
print("a[0:5:1] ", a[:5]) # yukaridaki ifadeye ozdes
print("a[:5:]: ", a[:5]) # iki onceki ve yukaridaki ifadeye ozdes
print("a[7:]: ", a[7:])
print("a[3:7]: ", a[3:7])
print("a[4:-1:2]: ", a[0:-1:2])
print("a[::4]: ", a[::4])
print("a[1:-1]: ", a[1:-1])
b = a[1:-1]
print("b: ", b)
b[2] = 0.1
print("Yeni b: ", b)
print("a: ", a)

Fonksiyonlardan Veri Toplamak

Bölümün başındaki örnekte tanımladığmız f(x) fonksiyonunu çok sayıda x'e aynı anda uygulamak için bu kez $NumPy$ dizilerinden yardım alalım.

In [ ]:
def f(x):
    return x**3 # ornek fonksiyon
n = 5 # nokta sayisi
x = np.linspace(0, 1, n)
y = np.zeros(n)
for i in range(n):
    y[i] = f(x[i])
print("f(x) = y: ", y)
# y dizisini olusturmak icin bir baska alternatif
y2 = np.array([f(xi) for xi in x])
print("f(x) = y2: ", y2)

Listeler için döngü kullanmaksızın kısa oluşturma şekillerinin mümkün olduğunu (list comprehensions) daha önce görmüştük. Bu yapılar nümerik diziler için kullanılamazlar. Ancak her zaman bu yapıları kullanarak bir liste oluşturup onu nümerik diziye dönüştürmeniz mümkündür. Yukarıdaki örnekte y2 dizisi kullanılan döngüye alternatif olarak bu yapıyla oluşturulup sonradan diziye dönüştürülmüştür. Ancak dizilerin asıl gücü fonksiyonun kolayca tüm diziye uygulanabilmesinden (vektörleştirme) gelir!

In [ ]:
# y dizisini olusturmak icin optimum alternatif: vektorlestirme
y3 = f(x)
print("f(x) = y3: ", y3)
In [ ]:
x**3
In [ ]:
import numpy as np
gezegenler = ('Merkur','Venus','Dunya','Mars','Jupiter','Saturn','Uranus','Neptun')
kutleler = [3.3011e23, 4.8675e24, 5.9724e24, 6.4171e24, 1.898187e27, 5.68317e26, 8.6813e25, 1.02413e26] # kg
gezegen_kutleleri = np.array(kutleler)
print(gezegen_kutleleri / kutleler[gezegenler.index('Jupiter')])

Diziler Üzerinde Fonksiyonların Kullanımı: Vektörleştirme

Örneğin tanımlı $f(x)$ fonksiyonu $sin(x) cos(x) (e^{-x^2}) + 2 + x^2$ olsun ve bu fonksiyonu $x_0, x_1, x_2, … , x_{n-1}$ şeklinde verilen $n$ tane $x$ değerine uygulamak istiyor olalım. x'in n tane eleman içeren bir nümerik dizi olarak tanımlanması durumunda aşağıdaki tek satırı yazmamız bu işlem için yeterlidir.

In [ ]:
from math import pi
from numpy import linspace, sin, cos, exp
n = 32
x = linspace(0, pi, n)
r = sin(x)*cos(x)*exp(-1*x)**2 + 2 + x**2 
print(r)

Ancak vektörel olarak tanımlanmamış, skalerlere uygulanabilen fonksiyonları (örneğimizde $math$ fonksiyonlarını) bu şekilde vektörlere uygulayamazsınız!

In [ ]:
from math import pi, sin, cos, exp
from numpy import linspace 
n = 32
# degisken skalerse math fonksiyonu bildiginiz sekilde calisir
x2 = pi / 2
print(sin(x2))
x = linspace(0, pi, n)
# vektorelse bu kez hata verir
#r = sin(x)*cos(x)*exp(-1*x)**2 + 2 + x**2 
#print(r)

Aşağıdaki iki örnekte aynı fonksiyon x nümerik dizisi üzerine skaler (1) ve vektörel (2) olarak uygulanıyor. Hangisini kodlamanın daha kolay olduğu açıktır ve de vektörel örnek daha hızlı çalışır!

In [ ]:
from math import exp
N = 5; x = np.zeros(N); y = np.zeros(N)
dx = 2.0 / (N-1) # aralik uzunlugu
for i in range(N):
    x[i] = dx*i
    y[i] = exp(-x[i])*x[i]
print("x: ", x)
print("y: ", y)
In [ ]:
import numpy as np
N = 5
x = np.linspace(0,2,N)
y = np.exp(-x)*x
print("x: ", x)
print("y: ", y)

Bir $f$ fonksiyonu, eğer $x$ nümerik dizisinin tüm elemanları için $f(x)$ şeklinde çalıştırılabiliyor ve sonucu $y = f(x)$ nümerik dizisi oluyorsa bu fonksiyona vektörleştirilmiş fonksiyon denir.

In [ ]:
import numpy as np
def f(x):
    return x**4*np.exp(-x)
x = np.linspace(-3,3,25)
y = f(x)
print("x: ", x)
print("y: ", y)
x2 = 5
print("f(x2) = ",f(x2))

Dizilerle İşlemler

Dizi Kopyalama

$x$ bir nümerik dizi olsun. $x = a$ ifadesinin $a$ isminin atıfta bulunduğu diziye $x$ isminin de atıfta bulunması anlamına geldiğini bu nedenle $x$ dizisinde yapılacak bir değişikliğin $a$ dizisini de etkileyeceğini görmüştük. $x$ dizisini, üzerinde ypaılacak bir değişikliğin $a$ dizisini etkilemeden, aynı içeriğe sahip bir dizi olarak yaratabilmenin yolu $a$ dizisinin bir kopyasını oluşturup, ismini $x$ olarak belirlemektir.

In [ ]:
import numpy as np
a = np.array([1, 2, 3.5])
x = a
print("Degisiklik oncesi a dizisi: ", a)
print("Degisiklik oncesi x dizisi: ", x)
print("---------------------------------------")
x[-1] = 3
print("x'teki degisiklik sonrasi a dizisi: ", a)
print("x'teki degisiklik sonrasi x dizisi: ", x)
print("---------------------------------------")
# Bunun yerine a'nin icerigini x'e kopyalayarak
# d dizisi olusturmak mumkundur
x = a.copy()
print("Icerik kopyalama sonrasi a dizisi: ", a)
print("Icerik kopyalama sonrasi x dizisi: ", x)
print("---------------------------------------")
x[-1] = 9
print("x'teki degisiklik sonrasi a dizisi: ", a)
print("x'teki degisiklik sonrasi x dizisi: ", x)
In [ ]:
x = a[:]
print("Dilimleme islemi sonrasi a dizisi: ", a)
print("Dilimleme islemi sonrasi x dizisi: ", x)
print("---------------------------------------")
x[-1] = 12
print("x'teki degisiklik sonrasi a dizisi: ", a)
print("x'teki degisiklik sonrasi x dizisi: ", x)

Listelerde durum aynı değildir; zira listeler değiştirilebilir ("mutable") nesnelerdir. Dilimleme işlemi içerik kopyalamayla benzer şekilde çalışır. Oysa ki diziler değiştirilemez ("immutable") nesnelerdir.

In [ ]:
b = [1, 2, 3.5]
c = b[:]
c[-1] = 3
print("b: ",b)
print("c: ",c)

Dizilerle Hızlı İşlemler

Konu nümerik diziler olunca, $a$ ve $b$'nin her ikisinin de eşit uzunluklu birer nümerik dizi olması durumunda $a += b$ ifadesiyle $a = a + b$ ifadesi arasında ciddi bir fark oluşmaktadır. Zira $a += b$ ifadesi, $a$ dizisinin her bir elemanını $b$ dizisinde karşılık geldiği eleman kadar arttırırken, $a = a + b$ ifadesi, $a$ dizisiyle $b$ dizisini toplayıp ara bir nümerik dizi oluşturmakta ve bu dizinin adını $a$ olarak değiştirmektedir. Söz konusu olan kısa iki dizi olduğu zaman problem çok büyük sayılmaz. Ancak bilim ve mühendislik uygulamaları çoğu zaman çok sayıda ve oldukça fazla eleman içeren matrisler üzerinde işlem yapmaya dayandığından önemi bir hafıza ve hız problemiyle karşılaşma olasılığı ortaya çıkar.

In [ ]:
import numpy as np
x = np.linspace(0.0, 5.0, 10)
a = (3*x**4 + 2*x + 4)/(x + 1)
print("a = ", a)

Yukarıdaki ifadede sırasıyla; 1) $r_1 = x^4$, 2) $r_2 = 3 r_1$, 3) $r_3 = 2 x$, 4) $r_4 = r_2 + r_3$, 5) $r_5 = r_4 + 4$, 6) $r_6 = x + 1$, 7) $r_7 = \frac{r_5}{r_6}$ ve sonuç olarak $a = r_7$ şeklinde 7 tane ara dizi oluşmaktadır. Oysa aşağıdaki ifadeler toplamda "çirkin" görünse de, $x$'i kopyalama, 4. kuvvetini alma, 2 ile çarpma ve 1 ekleme sırasında oluşan, sadece dört yeni nümerik dizi üzerinden yukarıdaki işlemi daha efektif bir şekilde gerçekleştirir. Çok daha fazla sayıda (örneğin Gaia kataloğundaki yıldızların sağ ve dik açıklıkları gibi) eleman barındıran dizilerle yapılan işlemlerde (Gaia kataloğundakki yıldızların bir t anında herhangi bir gözlemevi için gökyüzündeki konumları gibi) bu tür bir yazım büyük fark yaratabilmektedir.

In [ ]:
a = x.copy()
a **= 4
a *= 3
a += 2*x
a += 4
a /= x + 1
print("a = ", a)

Dizi Oluşturma Üzerine 2 Faydalı İpucu: 1) NumPy'da nümerik dizi oluştururken $zeros$ ve $ones$ fonksiyonlarının yanı sıra $copy$ fonksiyonu da sıkça kullanılır. $zeros$ fonksiyonunu $a = zeros(x.shape, x.dtype)$ şeklinde kullanmak $a$'nın $x$ ile aynı yapıda ve aynı tür elemanlar içeren ama sadece $0$'lardan oluşan bir dizi oluşturulmasını sağlar. 2) $a = asarray(a)$ yapısı ise $a$ bir dizi ise hiçbir değişiklik yapmazken, $a$'nın bir liste ya da demet değişken gibi bir dizi elemandan oluşan bir nesne türü (ing. iterable) olması durumunda onu bir nümerik diziye çevirir.

Başa Dön

Nümerik Dizilerde İndeksleme ve Dilimleme İşlemleri 2

Nümerik dizilerde indeksleme ve dilimleme işlemlerinde getirdiği pratik kullanım faydaları açısından $a[range(f:t:i)]$ yapsını incelemek gerekir. Bu yapı $a[f:t:i]$ yapısyla aynıdır ve $a$ dizisinin $f$ indeksinden başlayıp $t$ indeksine kadar ($t$ hariç), $i$ büyüklüğündeki adımlarla elemanlarının alınması ve istenirse değiştirilmesine yarar.

In [ ]:
import numpy as np
a = np.linspace(1,8,8)
print("a = ", a)
a[[1,6,7]] = 10 # 1., 6. ve 7. indekslerin değerini 10 yap
print("a[[1,6,7]] = 10 -->", a)
# range 2 ile 8 indeksler arasinda (8 haric) 3er atlayarak indeks degerlerini uretir
a[range(2,8,3)] = -2 
print("a[range(2,8,3)] = -2 -->", a)

Daha da pratik ve oldukça kullanışlı bir indeksleme türü de boolean ifadelere dayanır. Aşağıda verilen örneklerde göreceğiniz gibi boolean ifadeler de indeksleme için kullanılabilir.

In [ ]:
import numpy as np
a = np.linspace(-4,5,10)
print("a :", a)
a_negatif = a[a < 0].copy()
print("a_negatif: ", a_negatif)
# negatif elemanlari a dizisinin maksimumu degeri yap
a[a < 0] = a.mean()
print("a[a < 0] = a.mean() -->", a)
# 1., 6. ve 7. indekslerin değerini 10 yap
a[[1,6,7]] = 10 
print("a[[1,6,7]] = 10 -->", a)
# a'daki 10lari verilen baska bir diziden sirayla secilen elemanlarla degistir
a[a == 10] = [10, 20, 30] 
print("a[a == 10] = [10, 20, 30] -->", a)

Aşağıdaki örnekte olduğu gibi bir fonksiyonu bir dizinin belirli bir bölümüne uygulamak, indeksler, boolean ifadeler, dilimlemeler ve temel $NumPy$ fonksiyonları kullanarak mümkündür. Aşağıdaki örnekte $f(x) = x e^x$ fonksiyonu verilen $a$ dizisinin değeri $2$ ile $2.5$ arasındaki elemanlarına uygulanmaktadır. Bu aralığa $2$ ve $2.5$ sayılarını da dahil etmek için çok küçük ($10^{-10}$) bir tolerans değeri seçilmiş ve karşılaştırma bu tolerans değeri üzerinden yapılmıştır. Daha önceki bölümlerde görüldüğü gibi kayan noktalı (ing. float) sayılarla karşılaştırmada eşitlik ($==$, $<=$ ya da $>=$) yerine küçük tolerans değerleri kullanılarak yapılan büyüklük ve küçüklük karşılaştırmaları, yuvarlama kaynaklı hatalardan kaçınılmasını sağlar. İki koşulu aynı anda uygulamak üzere bu koşulları ayrı ayrı uygulayıp sonuçlarının kesişimini $np.intersect1d$ fonksiyonu ile alabilirsiniz.

In [ ]:
import numpy as np
def f(x):
    return x*np.exp(x)

a = np.linspace(1,3,15)

tolerans = 1e-10
# concetanate((a1,a2,a3,...)) aynı boyuta (büyüklüğe değil) sahip birden fazla
# NumPy dizisini ucuca ekleyerek birleştirir.
print(f(np.concatenate((a[a > 2-tolerans], a[a < 2.5+tolerans]))))
# Ayrica iki kosulu ayni anda uygulamak uzere bu kosullari ayri ayri uygulayip 
# sonuclarinin kesisimini np.intersect1d fonksiyonu ile de alabilirsiniz.
print("a > 2 ve a < 2.5 :", np.intersect1d(a[a > 2-tolerans], a[a < 2.5+tolerans]))
print(f(np.intersect1d(a[a > 2-tolerans], a[a < 2.5+tolerans])))
# Bitwise mantiksal operatörler de ayni amacla kullanilabilir
print("a > 2 ve a < 2.5 :", a[(a > 2-tolerans) & (a < 2.5+tolerans)])

Dizilerde Tür Kontrolü: Isinstance

NumPy nümerik dizilerinin türü $ndarray$ 'dir.

In [ ]:
a = np.linspace(-1, 1, 3)
a
In [ ]:
type(a)

Bazen kodunuzun içinde bir değişkenin türünü kontrol etmek ve değişkenin türüne göre değişen işlemler yapmanız gerekebilir. Bir değişkenin türünü kontrol etmek için $isinstance$ fonksiyonu kullanılır.

In [6]:
import numpy as np
a = np.linspace(-1, 1, 3)
print("a : ", a)
print("type(a) : ", type(a))
print("isinstance(a, np.ndarray) -->", isinstance(a, np.ndarray))
print("type(a) == np.ndarray --> ", type(a) == np.ndarray)
# a float ya da int turu mu?
print(a)
print("isinstance(a[0], (float, int)) -->",  isinstance(a[0], (int,float)))
a :  [-1.  0.  1.]
type(a) :  <class 'numpy.ndarray'>
isinstance(a, np.ndarray) --> True
type(a) == np.ndarray -->  True
[-1.  0.  1.]
isinstance(a[0], (float, int)) --> True

Aşağıda, gelen değişkenin türüne göre farklı bir işlem yapan (istenen türde 2 döndüren), istenen türlerden birinde değişken gelmiyorsa tür hatası ($TypeError$) veren bir örnek fonksiyon görüyorsunuz.

In [7]:
import numpy as np  
def f(x):
    if isinstance(x, (float, int)):
        return 2
    elif isinstance(x, np.ndarray):
        return np.zeros(x.shape, x.dtype) + 2
    else:
        raise TypeError\
        ("x <int>, <float> ya da <np.ndarray>, turlerinden biri olmali {:s} degil!".\
         format(str(type(x))))

print("f(x = 5) -->", f(x =5))
print("f(x = np.arange(-2, 2, 0.5)) -->", f(x = np.arange(-2,2,0.5)))
print("f(x = '5') -->", f(x = '5'))
f(x = 5) --> 2
f(x = np.arange(-2, 2, 0.5)) --> [2. 2. 2. 2. 2. 2. 2. 2.]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [7], in <cell line: 14>()
     12 print("f(x = 5) -->", f(x =5))
     13 print("f(x = np.arange(-2, 2, 0.5)) -->", f(x = np.arange(-2,2,0.5)))
---> 14 print("f(x = '5') -->", f(x = '5'))

Input In [7], in f(x)
      6     return np.zeros(x.shape, x.dtype) + 2
      7 else:
----> 8     raise TypeError\
      9     ("x <int>, <float> ya da <np.ndarray>, turlerinden biri olmali {:s} degil!".\
     10      format(str(type(x))))

TypeError: x <int>, <float> ya da <np.ndarray>, turlerinden biri olmali <class 'str'> degil!

Bazı Numpy Fonksiyonlarına Örnekler

Numpy Matemtaiksel Fonksiyonları

Numpy dizi nesneleri (numpy.ndarray) üzerine uygulanabilen matematik fonksiyonlarının tam bir listesinden sık kullanılan bazıları için örnekler aşağıda verilmektedir. Tüm bu fonksiyonlar $numpy$ modülü import edildikten sonra fonksiyonun gerek duyduğu argümanlar sağlanarak kullanılabilir ve diziler üzerinde çalıştıkları için vektöreldir.

Trigonometrik ve Hiperbolik Fonksiyonlar

Trigonometrik fonksiyonların argümnanı olan açılar radyan biriminde sağlanmalıdır. Numpy derece birimindeki açıları radyana çevirmek için iki fonksiyon sağlar: $radians$ ve $deg2rad$.

In [ ]:
import numpy as np
acilar = np.linspace(0,90,7)
acilar_rad = np.radians(acilar)
print("acilar [derece] =", acilar)
print("acilar [radyan] =", acilar_rad)
print("sin(acilar) =", np.sin(acilar_rad))
print("acilar [radyan] = arcsin(sin(acilar)) = ", np.arcsin(np.sin(acilar_rad)))
print("acilar [derece] = arcsin(sin(acilar)) = ", np.rad2deg(np.arcsin(np.sin(acilar_rad))))

İki dik kenarı ayrı dizilerde verilen üçgenler için hipotenüslerini hesaplayan bir fonksiyon ($hypot$) da bulunmaktadır. Aşağıda dik kenarları ve hipotenüsü tam sayılar olan dik üçgenler için bir örnek verimiştir. Bu tür sayılara Pisagor üçlüleri adı verilir.

In [8]:
a = np.array([3, 5, 7, 8, 20])
b = np.array([4, 12, 24, 15, 21])
print("c = sqrt(a^2 + b^2) = ", np.sqrt((a**2 + b**2)))
print("c = np.hypot(a,b) = ", np.hypot(a,b))
c = sqrt(a^2 + b^2) =  [ 5. 13. 25. 17. 29.]
c = np.hypot(a,b) =  [ 5. 13. 25. 17. 29.]

Hiperbolik fonksiyonlar

Hiperbolik fonksiyonların tanımları kullanarak denetleme yapılabilir. Bu fonksiyonlar kullanılırken dikkat edilmesi gereken bir başka husus da, fonksiyonların tanım aralıklarıdır.

$$ sinh(x) = \frac{e^x - e^{-x}}{2} $$
In [9]:
x = np.arange(0, np.pi + np.pi/12, np.pi/6)
print("sinh(x) = ", np.sinh(x))
print("(e^x - e^-x) / 2 = ", (np.exp(x) - np.exp(-x)) / 2)
sinh(x) =  [ 0.          0.54785347  1.24936705  2.3012989   3.99869134  6.8176233
 11.54873936]
(e^x - e^-x) / 2 =  [ 0.          0.54785347  1.24936705  2.3012989   3.99869134  6.8176233
 11.54873936]
In [10]:
print("arctanh(x) = ", np.arctanh(x))
arctanh(x) =  [0.         0.58128501        nan        nan        nan        nan
        nan]
/tmp/ipykernel_31319/3044596009.py:1: RuntimeWarning: invalid value encountered in arctanh
  print("arctanh(x) = ", np.arctanh(x))

Yuvarlama Fonksiyonları

Çeşitli yuvarlama stratejilerine göre argüman olarak fonksiyona sağlanan bir sayısal diziyi, yine argüman olarak verilen sayıda basamağa yuvarlayan fonksiynlardır.

In [ ]:
x = np.arange(-np.pi - np.pi/12., np.pi + np.pi/12, np.pi/6)
print("round(x) :", np.round(x,2))
print("rint(x) :", np.rint(x)) ## en yakin tamsayiya yuvarlama
print("fix(x) :", np.fix(x)) ## tamdeger fonksiyonu: sayidan kucuk en yakin tamsayiya yuvarlama
print("floor(x): ", np.floor(x)) ## asagi yuvarlama
print("ceil(x): ", np.ceil(x)) ## yukari yuvarlama
print("trunc(x): ", np.trunc(x)) ## kesip atma

Diğer Fonksiyonlar

In [ ]:
x = np.linspace(0,1,11)
print("x = ", x)
print("toplam(x) = ", np.sum(x))
print("carpim(x) = ", np.product(x))
print("birikimli toplam(x) = ", np.cumsum(x)) # birikimli toplama
print("birikimli carpim (x) = ", np.cumprod(x[1:])) # birikimli carpma
print("farklar(x) = ", np.diff(x)) # farklar
print("e^x = ", np.exp(x))
print("ln(x) = ", np.log(x[1:]))
print("log10(x) = ", np.log(x[1:]) / np.log(10) )
print("log10(x) = ", np.log10(x[1:]))
x1 = np.power(3,range(5)) # 3'un 5'e kadarki kuvvetleri
print("x1 = ", x1) 
print("log_3(x1) = ", np.log(x1) / np.log(3))
x2 = np.power(range(5), 3) # 5'e kadar sayilarin 3. kuvvetleri 
print("x2 = ", x2)
In [11]:
u1 = np.array([1,2])
u2 = np.array([3,4])
print("u1 + u2", np.add(u1,u2))
print("u1 - u2", np.subtract(u1,u2))
print("u1 . u2 = ", np.dot(u1,u2)) # skaler carpim
print("u1 x u2 = ", np.cross(u1,u2)) # capraz carpim
print("u1 / u2", np.true_divide(u1,u2))
print("u1 / u2", np.floor_divide(u1,u2))
v1 = np.array([11,12,17])
v2 = np.array([5,4,3])
print("mod(v1,v2)) = ", np.mod(v1, v2))
print("divmod(v1,v2) = ", np.divmod(v1,v2)) # bolumler , kalanlarb
u1 + u2 [4 6]
u1 - u2 [-2 -2]
u1 . u2 =  11
u1 x u2 =  -2
u1 / u2 [0.33333333 0.5       ]
u1 / u2 [0 0]
mod(v1,v2)) =  [1 0 2]
divmod(v1,v2) =  (array([2, 3, 5]), array([1, 0, 2]))
In [13]:
x = np.linspace(-5,4,10)
y = np.arange(5,-5,-1)
z = np.array([2,3,5,-1,-6,7,9,1,6,10])
z = z.astype(float)
print("x =", x)
print("y =", y)
print("z =", z)
print("|x| =", np.fabs(x))
print("maksimum (x,y ) = ", np.maximum(x,y)) # karsilikli elemanlardan buyuk olanlar
print("minimum (x,y ) = ", np.minimum(x,y)) # karsilikli elemanlardan kucuk olanlar
print("maksimum (x,y,z) = ", np.maximum(np.maximum(x,y),z)) # karsilikli elemanlardan buyuk olanlar
x = [-5. -4. -3. -2. -1.  0.  1.  2.  3.  4.]
y = [ 5  4  3  2  1  0 -1 -2 -3 -4]
z = [ 2.  3.  5. -1. -6.  7.  9.  1.  6. 10.]
|x| = [5. 4. 3. 2. 1. 0. 1. 2. 3. 4.]
maksimum (x,y ) =  [5. 4. 3. 2. 1. 0. 1. 2. 3. 4.]
minimum (x,y ) =  [-5. -4. -3. -2. -1.  0. -1. -2. -3. -4.]
maksimum (x,y,z) =  [ 5.  4.  5.  2.  1.  7.  9.  2.  6. 10.]

Dizi Metotları

Numpy fonksiyonlarının yanı sıra bir de numpy dizileri üzerine uygulanabilen metotlar (attribute) bulunmaktadır. Bunlardan bazıları aşağıda örneklenmiştir.

In [ ]:
import numpy as np
x = np.linspace(-5,4,10)
print("size(x) = ", x.size)
print("itemsize(x) = ", x.itemsize) # byte cinsinden her bir elemanin uzunlugu
y = np.reshape(x,(2,5))
print("y = ", y)
print("y'nin boyutlari = ", y.shape)
z = np.array([1,3,-1,5,7,11,-2,6,4])
print("maks(z) = ", z.max())
print("min(z) = ", z.min())
print("indeks maks(z) = ", z.argmax())
xint = x.astype(int)
print("xint = ", xint)
print("ort(z) = ", z.mean()) # ortalama
print("std(z) = ", z.std()) # standart sapma
print("toplam(z) = ", z.sum()) # toplam
# siralama (sort) islemi verildigi yerde uygulandigindan
# oncelikle siralamayi yapmak gerekir
z.sort()
print("siralanmis z = ", z)
# Bu davranisi istemiyorsaniz numpy.sort() fonksiyonunu kullaanabilirsiniz
z = np.array([1,3,-1,5,7,11,-2,6,4])
z_sirali = np.sort(z)
print("siralanmis z = ", z_sirali)
print("z = ", z)

Python ve Matrisler

NumPy ve Matrisler

Python'da matrisler $NumPy$ dizileri ve $Matrix$ nesnesi kullanılarak oluşturulur ve manipüle edilir. Bir boyutlu $NumPy$ dizileri vektör olarak da adlandırılırken, birden fazla boyutu olan $NumPy$ dizileri ise matris olarak değerlendirilebilir. Bir $NumPy$ nümerik dizisinin boyutunu $shape$ metodunu kullanarak öğerenebilirsiniz. Bu dizi metodu bir $NumPy$ dizisinin her bir boyutunun uzunluğunu tutar, çıktısı her bir boyutun uzunluğunu veren bir demettir ($tuple$). Aynı metot dizinin boyutunu değiştirmek için de kullanılabilir.

In [14]:
# Matris uygulamalarini (ornegin toplamasini)
# uygun fonskiyonlar yazildigi muddetce listelerle dahi
# yapmak mumkundur.
a = [1,2,3]
b = [4,5,6]
print(a+b)
def listetopla(a,b):
    topliste = []
    for i in range(len(a)):
        topliste.append(a[i]+b[i])
    return topliste
print(listetopla(a,b))
[1, 2, 3, 4, 5, 6]
[5, 7, 9]
In [15]:
import numpy as np
print(np.array(a) + np.array(b))
[5 7 9]
In [16]:
import numpy as np
a = np.linspace(-1, 1, 6)
print("a = ", a)
print("a.shape = ", a.shape)
print("a.size = ", a.size)
a.shape = (2,3)
print("a.shape = (2,3) -->", a)
# shape alternatifi olarak reshape kullanilabilir
a = a.reshape(3,2)
print("a = a.reshape(3,2) -->", a)
# array uzunlugu (len) ile sekli (shape) ayni seyler degildir
print("len(a) = ", len(a))
a =  [-1.  -0.6 -0.2  0.2  0.6  1. ]
a.shape =  (6,)
a.size =  6
a.shape = (2,3) --> [[-1.  -0.6 -0.2]
 [ 0.2  0.6  1. ]]
a = a.reshape(3,2) --> [[-1.  -0.6]
 [-0.2  0.2]
 [ 0.6  1. ]]
len(a) =  3

Bir $NumPy$ dizisi gördüğünüz gibi bir matris saklamak ve $NumPy$ fonksiyonlarının kullanımı ile matris işlemleri yapmak için iyi bir araçtır. Ancak Python dizileri kullanarak da matrisleri saklayabilirsiniz. Üzerinde (özellikle ileri matris işlemleri gibi) işlem yapmak oldukça zor olsa da bu mümkündür.

In [ ]:
Cderece = [-30 + i*10 for i in range(5)]
Fderece = [9./5*C + 32 for C in Cderece]
tablo1 = [[C, F] for C, F in zip(Cderece, Fderece)]
# 2 x 5 bir tablo (matris)
print(tablo1)

Bu listeyi bir $NumPy$ dizisine çevirmek oldukça kolaydır ve $array$ fonksiyonu ile yapılır.

In [ ]:
tablo2 = np.array(tablo1)
print(tablo2)
print("type(tablo2): ", type(tablo2))
tablo2.shape

Yukarıdaki iki örnekteki $tablo1$ ve $tablo2$ hafızada oldukça farklı şekillerde tutulur. Biri standart bir liste iken diğeri bir $NumPy$ dizisidir. Bir liste olan $tablo1$ 'in 3 elemanı vardır. Her bir eleman iki elemanı olan birer liste nesnesidir ve bu iki eleman da birer reel sayı (float) nesnesidir. Bir $NumPy$ dizisi olan $tablo2$ ise hafızada 6 adet reel sayıdan oluşan tek bir dizidir. Dolayısı ile $tablo1$ hafızada farklı tür nesnelerden oluşan dağınık bir nesneye, $tablo2$ ise tek bir dizi nesnesine karşılık gelir. Bu nedenle (ve işlem fonksiyonelliği gibi başka nedenlerle!) matris işlemleri için $NumPy$ dizilerini kullanmak listelerden daha avantajlı ve çok daha hızlıdır!

Başa Dön

Matris Uygulamaları için Listeler ve Diziler

Liste ve dizi indekslemeleri daha önce gördüğünüz gibi benzer şekildedir. Temel $Python$ liste ve $NumPy$ dizi fonksiyonları ile aynı şekilde yapılan dilimleri kullanarak matris işlemlerini kolaylaştırmak da mümkündür.

In [ ]:
print("tablo1[1][0] = ", tablo1[1][0])
print("tablo2[3][1] = ", tablo2[3][1])
# Daha cok Tercih edilen bir dizi indekslemesi
print("tablo2[1,0] = ", tablo2[1,0])
print("tablo2.shape = ", tablo2.shape)

İki boyutlu bir NumPy dizisinin elemanlarını tek tek yazdırmanın (ya da başka bir şekilde işlemenin) yolu içiçe iki for döngüsü kullanmaktır.

In [ ]:
for i in range(tablo2.shape[0]):
    for j in range(tablo2.shape[1]):
        print("tablo2[{:d},{:d}] = {:g}".format(i, j, tablo2[i,j]))

Listelerin elemanlarına ulaşmak için dilimleme yöntemini kullandığımız gibi dizilerin alt dizilerine (satır ve sütun) ulaşmak için de dilimlemeyi aynı şekilde kullanabiliriz.

In [ ]:
print("2. sutun (indeks = 1) tum satirlar: ", tablo2[0:tablo2.shape[0], 1])
print("2. sutun, tum satirlar, alternatif 1: ", tablo2[0:, 1])
print("2. sutun, tum satirlar, alternatif 2: ", tablo2[:, 1])

Daha komplike bir örneğe daha büyük bir NumPy dizisi üzerinde bakalım.

In [ ]:
import numpy as np
t = np.linspace(1, 30, 30).reshape(5, 6)
print("t:\n")
print(t)
print()
print("2. satirdan (ind:1) sonuncuya (ind:-1) kadar (sonuncu haric) \
birer atlayarak ve 3. sutundan (ind:2) son sutuna kadar t:\n", \
      t[1:-1:2, 2:])
print()
print("Sondan ikinci satira kadar tum satirlar ve \
bastan son sutuna kadar birer atlayarak sutunlar:\n", t[:-2, :-1:2])

Matrix Nesnesi ve NumPy İle Matris İşlemleri

$NumPy$ 'da matris işlemlerini kolay ve hızlı yapabilmek üzere bir matris ($matrix$) nesnesi tanımlanmıştır. Bu nesne matris işlemlerini kolaylaştıran bazı özel metotlara da sahiptir.

In [ ]:
import numpy as np
# x1 bir np dizisidir
x1 = np.array([1,2,3], float)
print("x1:", x1)
# x2 ise bir np matrisidir
x2 = np. matrix(x1) # ya da np.mat(x1)
print("x2: ", x2)
# x3 x1 dizisinin transpoze matrisidir
x3 = x2.transpose()
print("x3:", x3)
print("type(x3): ", type(x3))
print("isinstance(x3, np.matrix) --> ", isinstance(x3, np.matrix))
print("----------------------------")
# 3x3 bir birim matris dizisi --> I3
A = np.eye(3)
print("type(A)", type(A))
print("A(dizi) = I[3x3] \n", A)
A = np.matrix(A)
print("type(A)", type(A))
print("A(matris) = I[3x3] \n", A)
print("----------------------------")
# 1x3 ve 3x3 iki matrisin carpimi --> 1x3 matris
print("x2 x A = ", x2*A)
# 3x3 ve 3x1 iki matrisin carpimi --> 3x1 matris
print("A x x3 = ", A*x3)

! Uyarı: $NumPy$ dizileriyle matris çarpımı $NumPy$ matris ($matrix$) nesneleriyle matris çarpımından farklıdır!

In [ ]:
import numpy as np
x1 = np.array([1,2,3], float)
A = np.matrix(np.eye(3))
print("type(x1): ", type(x1))
print("type(A): ", type(A))
# Bir matrisle bir diziyi carpamazsınız
#print("A*x1 =", A*x1)
# bir dizi ile digerini carpmayi deneyelim
A = (np.ones(9)).reshape(3,3)
print("A = ", A)
# [A[0,:]*x1, A[1,:]*x1, A[2,:]*x1] Matris carpimi degil!
print("A*x1 =",  A*x1)
B = A + 1
# A'nin her bir elemani B'nin ayni indeksteki elamaniyla carpiliyor!
print("A * B =", A*B)
A = np.mat(A); B = np.mat(B)
# Gercek matris carpimi!
print("A * B =", A*B)

Aşağıda bir matris çarpması örneği verilmiştir. Her ne kadar aynı işlem listeler (içiçe olmak kaydıyla) ve numpy dizilerinden yararlanılarak (döngüler kullanmak suretiyle) yapılabilirse de matrisler $numpy.matrix$ nesnesi olarak tanımlandığında bu işlem çok daha pratik hale gelir.

In [ ]:
import numpy as np
a = np.array([list(range(1,4)),list(range(4,7))])
A = np.matrix(a)
b = np.array([list(range(10,12)),list(range(20,22)), list(range(30,32))])
B = np.matrix(b)
print("A = ", A)[Başa Dön](#Diziler-ve-Matrisler)
print("B = ", B)
print("A x B = ", A*B)

Alıştırmalar

  1. $NumPy$ $min$, $max$ ve $average$ fonksiyonlarını kullanarak aşağıda verilen a dizisinin en küçük, en büyük ve ortalama değerlerini bulunuz. $argmin$ ve $argmax$ fonksiyonlarını kullanarak da, en küçük ve en büyük değerlerin denk geldiği indeksleri bulunuz.
$$ a = np.array([2.5, 3.2, 9.1, -12.2, 1.8, 23.4, -16.9, 1.3, 2.6]) $$
  1. Aynı dizi için bu kez $NumPy$ dizi nesneleri üzerinde tanımlı $min$, $max$, $mean$ metotlarını (ing. attribute) kullanarak aynı işlemleri gerçekleştiriniz. Bir önceki soru ile aradaki farklara dikkat ediniz.(EME: Fonksiyon yerine metot kullanma farkı dışında bir fark var ise, bu son cümlede daha açıklayıcı olmak gerekebilir. Başka bir fark yok ise son cümle kaldırılabilir.)

  2. Fahrenheit (F) dereceyi Kelvin'e (K) çeviren bir fonksiyon yazınız. Yazdığınız fonksiyonun liste, demet, NumPy dizisi (array), kayan noktalı veya tamsayı değişkenlere uygulanabilir olmasını $isinstance$ fonksiyonunu kullanarak sağlayınız. Fonksiyonunuzu farklı türde nesnelerle test ediniz.

  3. 0 ile 5 arasında (0 ile 5 dahil), 6 tam sayıdan oluşan, $b$ adında bir $NumPy$ dizisi oluşturunuz. a) $b$ dizisindeki her bir sayının faktöriyelini, $numpy.math.factorial$ fonksiyonu ile bir döngü dahilinde bulup, $bfact$ isimli bir başka $NumPy$ dizisine aktarınız. b) $b$ dizisinin tüm elemanlarının birden faktöriyellerini $scipy.special.factorial$ fonksiyonu ile tek bir kerede hesaplayıp $bfact\_yeni$ isimli yeni bir diziye aktarınız.

  4. İki boyutlu (satır ve sütundan oluşan) kendi yazacağınız herhangi bir $NumPy$ dizisindeki satır ve sütun sayılarını; $len$ fonksiyonu, dilimleme yöntemi ve $shape$ metodunu (ing. attribute) kullanarak elde edip, ekrana yazdırınız.

  5. Bir önceki soruda oluşturduğunuz $NumPy$ dizisinin tüm elemanlarını önce satır, sonra sütun üzerinden tarayarak sırayla ekrana yazdırınız. Her bir eleman, satır ve sütun indeksleri ile birlikte, sırası ile alt alta yazdırılmalıdır.

  6. Bir önceki soruda satır-sütun sırasıyla taradığınız iki boyutlu diziyi bu kez sütun-satır sırasında tarayınız. Yani çıktınızda önce 1. sütundaki sayılar alt alta ekrana yazılırken, sonra 2. sütuna geçilmelidir.

  7. $np.linspace$ fonksiyonunu kullanarak 1 ile 4 arasında, 9 kayan noktalı sayıdan oluşan bir $NumPy$ dizisi yarattıktan sonra, bu diziyi $np.reshape$ fonksiyonu ile $[3x3]$ bir diziye çeviriniz ve bu dizinin devriğini (transpozunu) alınız.

  8. Aşağıdaki denklem sisteminin, katsayılar matrisini ve çözümünü sırasıyla $A$ ve $R$ isimli birer $NumPy$ $matrix$ nesnesi olarak oluşturunuz. Daha sonra katsayılar matrisinin tersini ($A.I$) alarak, çözüm matrisi $R$ ile çarpınız ve bulacağınız çözüm matrisindeki $x, y, z$ bilinmeyenlerinin değerlerini ekrana yazdırınız.

$$ 2 x - 3 y + z = 1 $$$$ 5 x - 4 y - z = 2 $$$$ x + 2 z = -1 $$
  1. Bir önceki soruda A matrisinin tersini alırken A.I yerine $np.linalg.inv$ fonksiyonunu kullanınız. Sonuçlarınızın doğruluğunu karşılaştırınız.

  2. Son iki soruda çözdüğünüz denklem sistemini $np.linalg.solve$ fonksiyonu ile çözünüz. Sonuçlarınızın doğruluğunu karşılaştırınız.

  3. Aşağıdaki fonksiyonu hesaplayan bir Python fonksiyonu yazınız. Yazdığınız fonksiyonu; a) -$2\pi$ ile $2\pi$ arasında ve bu sayılar dahil olmak üzere 16 eşit aralıklı sayı içeren bir $NumPy$ dizisine uygulayınız. b) Aynı fonksiyonu, bu dizinin -$\pi$ ile $\pi$ arasındaki değerlerine koşul kullanarak uygulayınız. -$\pi$ ile $\pi$ değerlerini dahil etmek için yeterince küçük bir tolerans ($\epsilon$) değerini, -$\pi$-$\epsilon$ ve $\pi$+$\epsilon$ şeklinde iki ayrı koşul ile kullanınız. Bu iki ayrı koşulun sonuçlarının kesişimini almak için, $np.intersect1d$ fonksiyonunu kullanabilirsiniz.

$$ h(x) = \frac{1}{\sqrt{2 \pi}} e^{\frac{-1}{2}x^2} $$

Başa Dön