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 [1]:
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)
x listesi:  [0.0, 0.25, 0.5, 0.75, 1.0]
f(x) = y listesi:  [0.0, 0.015625, 0.125, 0.421875, 1.0]
(x,y) listesi:  [(0.0, 0.0), (0.25, 0.015625), (0.5, 0.125), (0.75, 0.421875), (1.0, 1.0)]

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 [6]:
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)
linspace ile olusturulan a4 dizisi:  [1. 2. 3. 4. 5.]
arange ile olusturulan a4 dizisi:  [1. 2. 3. 4. 5.]

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 [61]:
import numpy as np
a = np.arange(-1.,1.,0.5)
print("a = ", a)
a =  [-1.  -0.5  0.   0.5]

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 [63]:
b = np.linspace(-1, 0.5, 4)
print("b = ", b)
b =  [-1.  -0.5  0.   0.5]

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

In [66]:
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 [8]:
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[7:]: ", a[7:])
print("a[3:7]: ", a[3:7])
print("a[0:-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)
a:  [ 1.  2.  3.  4.  5.  6.  7.  8.  9. 10.]
a[0]: 1.00, a[-1]: 10.00, a[5]: 6.00
a[:5]:  [1. 2. 3. 4. 5.]
a[7:]:  [ 8.  9. 10.]
a[3:7]:  [4. 5. 6. 7.]
a[0:-1:2]:  [1. 3. 5. 7. 9.]
a[::4]:  [1. 5. 9.]
a[1:-1]:  [2. 3. 4. 5. 6. 7. 8. 9.]
b:  [2. 3. 4. 5. 6. 7. 8. 9.]
Yeni b:  [2.  3.  0.1 5.  6.  7.  8.  9. ]
a:  [ 1.   2.   3.   0.1  5.   6.   7.   8.   9.  10. ]

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 [12]:
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)
f(x) = y:  [0.       0.015625 0.125    0.421875 1.      ]
f(x) = y2:  [0.       0.015625 0.125    0.421875 1.      ]

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 [11]:
# y dizisini olusturmak icin optimum alternatif: vektorlestirme
y3 = f(x)
print("f(x) = y3: ", y3)
f(x) = y3:  [0.       0.015625 0.125    0.421875 1.      ]

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 [17]:
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)
[ 2.          2.09245396  2.17254544  2.2479337   2.32541897  2.41077249
  2.50869226  2.62284556  2.75596642  2.9099832   3.08615775  3.28522288
  3.50750949  3.75305833  4.02171395  4.31320055  4.62718053  4.96329781
  5.32120782  5.70059679  6.10119257  6.52276897  6.96514566  7.42818494
  7.91178683  8.41588313  8.94043149  9.48540956 10.05080986 10.63663535
 11.24289568 11.8696044 ]

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 [20]:
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)
x:  [0.  0.5 1.  1.5 2. ]
y:  [0.         0.30326533 0.36787944 0.33469524 0.27067057]
In [21]:
import numpy as np
N = 5
x = np.linspace(0,2,N)
y = np.exp(-x)*x
print("x: ", x)
print("y: ", y)
x:  [0.  0.5 1.  1.5 2. ]
y:  [0.         0.30326533 0.36787944 0.33469524 0.27067057]

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 [22]:
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)
x:  [-3.   -2.75 -2.5  -2.25 -2.   -1.75 -1.5  -1.25 -1.   -0.75 -0.5  -0.25
  0.    0.25  0.5   0.75  1.    1.25  1.5   1.75  2.    2.25  2.5   2.75
  3.  ]
y:  [1.62692849e+03 8.94624115e+02 4.75878670e+02 2.43160292e+02
 1.18224898e+02 5.39718790e+01 2.26885509e+01 8.52134511e+00
 2.71828183e+00 6.69832037e-01 1.03045079e-01 5.01572428e-03
 0.00000000e+00 3.04219056e-03 3.79081662e-02 1.49459730e-01
 3.67879441e-01 6.99474602e-01 1.12959644e+00 1.62980952e+00
 2.16536453e+00 2.70126685e+00 3.20644526e+00 3.65612428e+00
 4.03275254e+00]

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 [3]:
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)
Degisiklik oncesi a dizisi:  [1.  2.  3.5]
Degisiklik oncesi x dizisi:  [1.  2.  3.5]
---------------------------------------
x'teki degisiklik sonrasi a dizisi:  [1. 2. 3.]
x'teki degisiklik sonrasi x dizisi:  [1. 2. 3.]
---------------------------------------
Icerik kopyalama sonrasi a dizisi:  [1. 2. 3.]
Icerik kopyalama sonrasi x dizisi:  [1. 2. 3.]
---------------------------------------
x'teki degisiklik sonrasi a dizisi:  [1. 2. 3.]
x'teki degisiklik sonrasi x dizisi:  [1. 2. 9.]

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 [14]:
import numpy as np
x = np.linspace(0.0, 5.0, 10)
a = (3*x**4 + 2*x + 4)/(x + 1)
print("a = ", a)
a =  [  4.           3.46942975   5.11327702  11.43055556  25.32538669
  49.80912612  87.93162393 142.75935279 217.36692702 314.83333333]

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 [16]:
a = x.copy()
a **= 4
a *= 3
a += 2*x
a += 4
a /= x + 1
print("a = ", a)
a =  [  4.           3.46942975   5.11327702  11.43055556  25.32538669
  49.80912612  87.93162393 142.75935279 217.36692702 314.83333333]

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 [17]:
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)
a =  [1. 2. 3. 4. 5. 6. 7. 8.]
a[[1,6,7]] = 10 --> [ 1. 10.  3.  4.  5.  6. 10. 10.]
a[range(2,8,3)] = -2 --> [ 1. 10. -2.  4.  5. -2. 10. 10.]

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 [2]:
import numpy as np
a = np.linspace(-4,5,10)
print("a[a < 0] -->", a[a < 0])
# negatif elemanlari a dizisinin maksimumu degeri yap
a[a < 0] = a.max()
print("a[a < 0] = a.max() -->", 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 < 0] --> [-4. -3. -2. -1.]
a[a < 0] = a.max() --> [5. 5. 5. 5. 0. 1. 2. 3. 4. 5.]
a[[1,6,7]] = 10 --> [ 5. 10.  5.  5.  0.  1. 10. 10.  4.  5.]
a[a == 10] = [10, 20, 30] --> [ 5. 10.  5.  5.  0.  1. 20. 30.  4.  5.]

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 [7]:
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])))
[14.7781122  18.26519242 22.47475904 27.54647625 33.64586548 40.96895335
 49.74773732 60.25661077  2.71828183  3.58367402  4.65075101  5.96104841
  7.56410245  9.51892779 11.89576037 14.7781122  18.26519242 22.47475904
 27.54647625]
[14.7781122  18.26519242 22.47475904 27.54647625]

Dizilerde Tür Kontrolü: Isinstance

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

In [24]:
a = np.linspace(-1, 1, 3)
a
Out[24]:
array([-1.,  0.,  1.])
In [25]:
type(a)
Out[25]:
numpy.ndarray

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 [34]:
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("isinstance(a, (float, int)) -->",  isinstance(a, (float, int)))
a :  [-1.  0.  1.]
type(a) :  <class 'numpy.ndarray'>
isinstance(a, np.ndarray) --> True
type(a) == np.ndarray -->  True
isinstance(a, (float, int)) --> False

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 [57]:
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)
<ipython-input-57-b0cc57be672c> in <module>()
     10 print("f(x = 5) -->", f(x =5))
     11 print("f(x = np.arange(-2, 2, 0.5)) -->", f(x = np.arange(-2,2,0.5)))
---> 12 print("f(x = '5') -->", f(x = '5'))

<ipython-input-57-b0cc57be672c> in f(x)
      6         return np.zeros(x.shape, x.dtype) + 2
      7     else:
----> 8         raise TypeError        ("x <int>, <float> ya da <np.ndarray>, turlerinden biri olmali {:s} degil!".         format(str(type(x))))
      9 
     10 print("f(x = 5) -->", f(x =5))

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

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 [67]:
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 [68]:
Cderece = [-30 + i*10 for i in range(3)]
Fderece = [9./5*C + 32 for C in Cderece]
tablo1 = [[C, F] for C, F in zip(Cderece, Fderece)]
# 2 x 3 bir tablo (matris)
print(tablo1)
[[-30, -22.0], [-20, -4.0], [-10, 14.0]]

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

In [70]:
tablo2 = np.array(tablo1)
print(tablo2)
print("type(tablo2): ", type(tablo2))
[[-30. -22.]
 [-20.  -4.]
 [-10.  14.]]
type(tablo2):  <class 'numpy.ndarray'>

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 [71]:
print("tablo1[1][0] = ", tablo1[1][0])
print("tablo2[1][0] = ", tablo2[1][0])
# Daha cok Tercih edilen bir dizi indekslemesi
print("tablo2[1,0] = ", tablo2[1,0])
print("tablo2.shape = ", tablo2.shape)
tablo1[1][0] =  -20
tablo2[1][0] =  -20.0
tablo2[1,0] =  -20.0
tablo2.shape =  (3, 2)

İ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 [73]:
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]))
tablo2[0,0] = -30
tablo2[0,1] = -22
tablo2[1,0] = -20
tablo2[1,1] = -4
tablo2[2,0] = -10
tablo2[2,1] = 14

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 [74]:
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])
2. sutun (indeks = 1) tum satirlar:  [-22.  -4.  14.]
2. sutun, tum satirlar, alternatif 1:  [-22.  -4.  14.]
2. sutun, tum satirlar, alternatif 2:  [-22.  -4.  14.]

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

In [2]:
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])
t:

[[ 1.  2.  3.  4.  5.  6.]
 [ 7.  8.  9. 10. 11. 12.]
 [13. 14. 15. 16. 17. 18.]
 [19. 20. 21. 22. 23. 24.]
 [25. 26. 27. 28. 29. 30.]]

2. satirdan (ind:1) sonuncuya (ind:-1) kadar (sonuncu haric) birer atlayarak ve 3. sutundan (ind:2) son sutuna kadar t:
 [[ 9. 10. 11. 12.]
 [21. 22. 23. 24.]]

Sondan ikinci satira kadar tum satirlar ve bastan son sutuna kadar birer atlayarak sutunlar:
 [[ 1.  3.  5.]
 [ 7.  9. 11.]
 [13. 15. 17.]]

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 [4]:
import numpy as np
# x1 bir np dizisidir
x1 = np.array([1,2,3], float)
# 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("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)
x2:  [[1. 2. 3.]]
type(x3):  <class 'numpy.matrixlib.defmatrix.matrix'>
isinstance(x3, np.matrix) -->  True
----------------------------
type(A) <class 'numpy.ndarray'>
A(dizi) = I[3x3] 
 [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
type(A) <class 'numpy.matrixlib.defmatrix.matrix'>
A(matris) = I[3x3] 
 [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
----------------------------
x2 x A =  [[1. 2. 3.]]
A x x3 =  [[1.]
 [2.]
 [3.]]

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

In [98]:
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.zeros(9) + 1).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)
type(x1):  <class 'numpy.ndarray'>
type(A):  <class 'numpy.matrixlib.defmatrix.matrix'>
A =  [[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]
A*x1 = [[1. 2. 3.]
 [1. 2. 3.]
 [1. 2. 3.]]
A * B = [[2. 2. 2.]
 [2. 2. 2.]
 [2. 2. 2.]]
A * B = [[6. 6. 6.]
 [6. 6. 6.]
 [6. 6. 6.]]

Alıştırmalar

  1. $NumPy$ $min$, $max$ ve $average$ fonksiyonlarını kullanarak aşağıda verilen a dizisinin minimum, maksimum ve ortalama değerlerini 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 sorudaki ile aradaki farklara dikkat ediniz.

  2. Fahrenheit (F) dereceyi Kelvin'e (K) çeviren bir fonksiyon yazınız. $isinstance$ fonksiyonunu kullanarak yazdığınız bu fonksiyonun bir liste, demet ya da NumPy dizisi (array) içinde gönderilen değerlerin yanı sıra herhangi bir tam sayı ya da kayan noktalı sayı nesnesine de uygulanabilmesini 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 yaratınız.a) Bu dizideki her bir sayının faktöriyelini $numpy.math.factorial$ fonksiyonu ile bir döngü dahilinde bulup $bfact$ isimli bir başka $NumPy$ dizisinde toplayınız. b) Bu dizinin tümünün birden faktöriyellerini $scipy.special.factorial$ fonksiyonu ile tek bir kerede hesaplayıp $bfact\_yeni$ isimli dizide toplayı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ı dilimlemeler, $len$ fonksiyonu ve $shape$ metodunu (ing. attribute) kullanarak elde edip, ekrana yazdırınız.

  5. Herhangi iki boyutlu (5. soruda oluşturacağınız gibi) bir $NumPy$ disininin tüm elemanlarını önce satır, sonra sütun üzerinden tarayarak sırayla ekrana yazdırınız. Çıktıda önce 1. satırdaki sayılar sırayla alt alta ekrana yazılmalı, sonra 2. satıra geçilip aynı sırayla devam edilmelidir.

  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 (transpozesini) alınız.

  8. Aşağıdaki denklem sistemini çözmek üzere bu 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 $A.I$ ile katsayılar matrisinin tersini 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 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 uygulayınız. $\pi$ ile $\pi$ değerlerini dahil etmek için yeterince küçük bir tolerans değeri kullanınız. İ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.

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

Başa Dön