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
pandas
Python programlama dili için yazılmış, BSD-lisanslı açık kaynak kodlu, yüksek performanslı, kolay kullanılabilen veri yapıları ve analiz araçları sağlayan bir kütüphanedir (pandas dokümantasyonu). Üzerine kurulu olduğu, bir yaygın sayfa yapısına benzer, sütunların isimlendirildiği veri çerçevesi nesnesi (ing. DataFrame
) sayesinde veriyle kolay ve hızlı etkileşim sağlar.
Seriler (series
) etiketlendirlmiş birer bir boyutlu (1D) dizidir. Yapısı sözlük (dictionary) yapısına benzer, tıpkı numpy dizileri gibi sabit bir uzunluğa sahiptir. Değer almayan ya da 'N/A', 'NaN', '-' gibi nümerik olmayan değerler alan sütunların yönetimine de olanak sağlar. Numpy ya da Python'un standart işlem ya da fonksiyonlarının üzerinde kullanılabildiği bir yapıdır. Örneğin sözlüklerde (dictionary) bu işlem ve fonksiyonların çok büyük bir bölümü kullanılamaz. pandas modülünün üzerine kurulu olduğu DataFrame
yapısının temelinde pandas serileri vardır. pandas serileri temel olarak veri çerçevelerinin sütunları olarak kullanılabilmekle birlikte kendi başlarına veri yapıları olarak da kullanılabilirler.
İşe bir pandas.Series
nesnesi oluşturarak başlayalım.
import pandas as pd
import numpy as np
gzgn_kutleler = pd.Series([0.330, 4.87, 5.97, 0.642, 1898, 568, 86.8, 102], \
index=['merkur', 'venus', 'dunya','mars','jupiter', 'saturn', 'uranus', 'neptun'], \
name="gezegenler")
print(gzgn_kutleler)
Görüldüğü gibi gzgn_kutleler pandas serisini oluşturmak üzere bir listeden sağlanan etiketler (labels
ya da index
) her bir gezegeni, değerler ise yine bir listeden sağlanan ($10^{24}$ kg cinsinden) kütle değerlerini göstermektedir. Değerleri bir numpy dizisi gibi itere edilebilir (iterable) başka bir değişken türünden de sağlamak mümkündür. Bu anlamda, pandas modülünün Series
nesnesi serbest olarak indekslenmiş birer numpy
dizisi gibi de düşünülebilir.
pandas serileri etiketsiz (ya da indekssiz) başlatılabileceği gibi, ki bu durumda indeksler 0'dan başlanarak tam sayılarla numaralandırılır, bir sözlük (dictionary) değişkeni üzerinden de tanımlanabilir. Yapıları nedeniyle Pandas series
nesnesi, özelleştirilmiş birer dictionary
(sözlük) nesnesi gibi de düşünülebilir. Her bir indeks söz konusu seride bir satırdaki değere karşılık gelmektedir.
import numpy as np
s1 = pd.Series(np.random.randn(5)*10)
print(s1)
yldz_mV = {'Sirius' : -0.5, 'Vega' : 0.0, 'Proxima' : 13.6}
print(type(yldz_mV), yldz_mV)
yldz_mV = pd.Series((yldz_mV))
print(yldz_mV)
print(type(yldz_mV))
pd.Series(yldz_mV, index=['Vega', 'Sirius', 'Altair', 'Proxima'])
İstendiği takdirde bir pandas serisine isim (name) de atanabilir. Bu amaçla serinin name
ya da rename
öznitelikleri kullanılır.
print(yldz_mV.name)
yldz_mV.name = "yildizlar"
print(yldz_mV)
Tıpkı bir numpy dizisi gibi pandas series
nesnesi üzerinde işlemler yapılabilir ve fonksiyonlar uygulanabilir, indeksleme ve dilimleme özellikleri kullanılabilir.
print(gzgn_kutleler[::2])
print(gzgn_kutleler > gzgn_kutleler['dunya'])
print(gzgn_kutleler[gzgn_kutleler < gzgn_kutleler['uranus']])
print(yldz_mV[1:-1])
print("-----------------")
print(yldz_mV[yldz_mV > yldz_mV.median()])
y1 = 'Vega'
y2 = 'Proxima'
print("{:s} ile {:s}'nin aki oranlari {:g}.".\
format(y1, y2, 10**(-0.4*(yldz_mV[y1]-yldz_mV[y2]))))
'Betelgeuse' in yldz_mV
print("Verilen yildizlarin ortalama parlakligi: {:g}, standart sapmasi: {:g}".\
format(yldz_mV.mean(), yldz_mV.std()))
s2 = pd.Series(np.random.randn(5)*2.5)
print("s2: \n", s2)
print("s2 + s2: \n" , s2 + s2)
print("s2^2: \n", s2**2)
Bu işlemlerde her iki tarafta aynı başlıkların (indekslerin ya da etiketlerin) olmaması sorunu da pandas tarafından etkin bir şekilde yönetilir.
a = s2[1:]
print(a)
s2[1:] + s2[:-2]
print(yldz_mV.name)
yldz_mV = yldz_mV.rename('mV')
print(yldz_mV.name)
pandas serilerine (series) yeni seriler `append` concat
fonksiyonuyla eklenir. Veriçerçevelerinin her seferinde tüm verilerin arka arkaya eklenerek yeniden oluşturulması sebebiyle append
metodu pandas 1.4.0
versiyonundan itibaren kullanımdan kaldırılmıştır.
print(gzgn_kutleler)
yeni_cisimler = {'pluto':0.0146, 'ay':0.073}
print("------------")
gzgn_kutleler = pd.concat([gzgn_kutleler,pd.Series(yeni_cisimler)],axis=0,sort=False)
print(gzgn_kutleler)
print("Saturn'un kutlesi, {:.4f} Jupiter kutlesidir.".format(gzgn_kutleler['saturn'] / gzgn_kutleler['jupiter']))
Pandas serileri her ne kadar veri üzerinde çalışmak için etkin ve hızlı bir yol sağlasa da Pandas'ın asıl veri nesnesi veri çerçeveleridir (DataFrame
). Seriler çoğu zaman hızlı bir şekilde veri çerçeveleri oluşturmak üzere ve her veri çerçevesinin sütunları olarak kullanılırlar ve bir boyutludurlar, veri çerçeveleri ise tablolar ve yaygın sayfalar (spreadsheet) olarak düşünülebilir. Bu yapıları nedeniyle Pandas DataFrame
nesnelerini, özelleştirilmiş birer dictionary
(sözlük) nesnesi olarak da düşünmek mümkündür. Her bir indeks söz konusu veri çerçevesinde bir sütuna karşılık gelir.
Bir DataFrame
pek çok açıdan sütun ve satırların isimlerinin olduğu yaygın sayfalara benzer. Yapıları gereği iki boyutuludur (2D). Pandas'la veri analizi büyük ölçüde veri çerçevesi nesnesi üzerine kurulu olduğundan en sık kullanılan nesneler de DataFrame
nesneleridir.
Veri çerçeveleri
Dolayısı ile Güneş Sistemi gezegenlerinin bazı özelliklerini saklamak istediğimiz gzgn_kutleler
serisi gibi yapıları saklamak için DataFrame
daha esnek ve kolay analize olanak sağlayan bir yapı sağlar. Bu seri daha geniş ve gezegenlere ilişkin başka özelliklerin de saklandığı bir gezegenler
veri çerçevesinin bir sütununu oluşturabilir.
gezegenler = {'Mg' : gzgn_kutleler,
'Rg' : pd.Series([2439.5, 6052., 6378., 1737.5, 3396., 71492., 60268., 25559., 24764., 1185.],\
index=['merkur','venus','dunya', 'ay', 'mars', 'jupiter', 'saturn', 'uranus', 'neptun','pluto']),\
'Prot' : pd.Series([1407.6, -5832.5, 23.9, 655.7, 24.6, 9.9, 10.7, -17.2, 16.1, -153.3],\
index=['merkur','venus','dunya', 'ay', 'mars', 'jupiter', 'saturn', 'uranus', 'neptun','pluto']),\
'Porb' : pd.Series([88.0, 224.7, 365.2, 27.3, 687.0, 4331, 10747, 30589, 59800, 90560], \
index=['merkur','venus','dunya', 'ay', 'mars', 'jupiter', 'saturn', 'uranus', 'neptun','pluto'])}
gunes_sistemi = pd.DataFrame(gezegenler)
gunes_sistemi
Veri çerçeveleri pandas serileri kullanılarak oluşturulabileceği gibi listelerden, numpy dizilerinden veya sözlüklerden de oluşturulabilir.
# Sozlukler uzerinden veri cercevesi olusturma
veri = {
'ogrno' : ['05','12','23','04'],
'arasinav' : [35, 72, 60, 100],
'final' : [70, 43, 57, 82]
}
ders1 = pd.DataFrame(veri)
print(ders1)
# Listeler uzerinden veri cercevesi olusturma
sehirler = pd.DataFrame(data = [
['01', 'Adana', 'Kebap'],
['34', 'Istanbul', 'Kalabalik'],
['06', 'Ankara', 'Memurlar'],
['35', 'Izmir', 'Gevrek'],
['44', 'Malatya', 'Kayisi'],
['16', 'Bursa', 'Uludag']
], columns = ['Plaka', 'Sehir', 'Ozellik'])
print(sehirler)
# Numpy dizileri uzerinden veri cercevesi olusturma
tarihler = pd.date_range('20220222', periods=5)
df = pd.DataFrame(np.random.randn(5, 4), index=tarihler, columns=list('ABCD'))
print(df)
# Birden fazla nesne turu kullanarak veri cercevesi olusturma
df2 = pd.DataFrame({'A': 1.,
'B': pd.Timestamp('20220222'),
'C': pd.Series(1, index=list(range(4)), dtype='float32'),
'D': np.array([3] * 4, dtype='int32'),
'E': pd.Categorical(["AST413", "AST415", "AST416", "AST515"]),
'F': 'Asterosismoloji'})
print(df2)
Veri çerçevelerinin tamamını, bir bölümünü, bir sütununu, bir satırını ya da bir elemanını görüntülemek için pek çok fonksiyon bulunduğu gibi listelerde ve dizilerde geçerli dilimleme ve indeksleme seçenekleri de kullanılabilmektedir.
print("Tun Gunes Sistemi")
print(gunes_sistemi)
print("---------------------")
print("Veri cercevesindeki ilk 5 gezegen")
print(gunes_sistemi.head())
print("---------------------")
print("Veri cercevesindeki son 3 gezegen")
print(gunes_sistemi.tail(3))
print("---------------------")
print("Kutleler : ")
print(gunes_sistemi['Mg']*1e24)
print("Ortalama Kutle: {:g} kg".format(gunes_sistemi['Mg'].mean()*1e24))
print("----------------------")
gunes_sistemi.info()
print(gunes_sistemi.columns)
cols = [col for col in gunes_sistemi.columns]
gunes_sistemi[cols].dtypes
describe
metodu ile veri setinin bazı istatistiksel parametrelerini görmek ve veri seti hakkında hızlı bir yorumda bulunmak da mümkün olabilir.
print(gunes_sistemi.describe())
Veri çerçevesini herhangi bir eksene göre sıralamak için sort_values
fonksiyonu kullanılır.
gunes_sistemi.sort_values(by='Rg')
İndeksleme ve dilimleme işlemleri tıpkı numpy
, list
, tuple
nesenlerinde ve pd.Series
nesnesinde yapıldığı şekliyle yapılabilir. Bunun dışındaki işlemler için:
df[col]
(ilgili sütundaki seriyi getirir)df.loc[label]
(ilgii satırdaki seriyi getirir)df.iloc[loc]
(ilgii satırdaki seriyi getirir)df[5:10]
(Veri çerçevesinin dilimlenmiş bölümünü getirir)df[kosul]
(Veri çerçevesinin koşula uyan bölümünü)yapıarı da kullanılabilir. Öncelikle numpy
dizilerindeki indeksleme tarzı indeksleme seçeneklerini gözden geçirelim.
gunes_sistemi[::2]
gunes_sistemi[-1:0:-2]
İndekslemede tamsayı indekslere başvurarak verilerin yeri üzerinden yukarıdaki gibi indeksleme yapılabileeği gibi (ing. implicit indexes), Series
ve DataFrame
nesnelerinin indeksleri (satır isimleri olarak da düşünülebilir) olduğu düşüncesinden hareketle bu indeksler üzerinden de dilimleme ve indeksleme (ing. explicit indexes) yapılabilir.
gunes_sistemi['jupiter':'pluto']
Bu durumda özellikle tamsayı indeksler seçilerek kullanıcı tarafından oluşturulmuş veri çerçeveleri ve seriler üzerinde çalışılırken, kullanıcının belirlediği indekslerin mi yoksa verinin bulunduğu yerin indekslerinin mi geçerli olduğu gibi bir soru ortaya çıkar. Bunu basit bir örnekle görelim.
seri_tamindeks = pd.Series(['merkur', 'dunya', 'jupiter'], index=[1, 3, 5])
#Indeksleme yapilirken kullanici tanimli indeksler kullanilirken
print(seri_tamindeks[1])
print("---------------")
#Dilimlemede verinin yerine dayali indeksler kullanilir
print(seri_tamindeks[0:2])
Bu karışıklığı önlemek için Pandas, bazı dizinleme şemalarını esas alan özel dizin oluşturucu özellikler (attributes
) sağlar. Bunları pandas nesneleri üzerinde tanımlı fonksiyonlar metotlar (methods
) olarak değil, o nesneye özel özellikler olarak düşünmek gerekir.
loc
özniteliği (attribute) her zaman kullanıcı tarafından verilen indekslere referans vermek için kullanılır.
# seri_tamindeks uzerinde bu kez kullanici indeksiyle veri secelim
print(seri_tamindeks.loc[5])
# Gunes Sistemi veri cercevesi uzerinde
# Herhangi satir(lar)i indeksiyle secmek icin
gunes_sistemi.loc['jupiter']
Dilimlemede kullanıcı tanımlı indeksler üzerinden dilimleme yapılırken verilen aralığın her iki ucu da (sonu da!) dilimlememey dahil edilirken, numpy dizisi (ya da liste) tarzı, verinin bulunduğu konuma göre yapılan indekslemede verilen aralığın sonu dilimlemeye dahil edilmez (örn. $dizi[1:4]$ şeklinde bir indekslemede dizinin 1, 2 ve 3 numaralı indekslerinde bulunan değerlerden bir dizi dilimi oluşturulur, 4 numaralı indeksteki değer alınmaz!).
# Veri cercevesinin sadece bir bolumunu secmek icin
gunes_sistemi.loc['jupiter':'merkur', ['Mg', 'Rg']]
gunes_sistemi.loc['ay', ['Prot','Porb']]
loc
kullanılarak sütun ya da satır adıyla seçim yapılabileceği gibi indeks numarası kullanılarak verinin bulundğu konum üzerinden de seçim yapılabilir; bunun için iloc
özniteliğine başvurulur.
# seri_tamindeks serisi uzerinde
print(seri_tamindeks.iloc[1])
print(seri_tamindeks.loc[1])
# seri_tamindeks serisi uzerinde
print(seri_tamindeks.iloc[2])
print(seri_tamindeks.iloc[0:2])
gunes_sistemi.iloc[4]
gunes_sistemi['Prot'] = gunes_sistemi['Prot'] / 23.90
gunes_sistemi
veriçerçevesindeki nesneleri yörünge dönemine göre sıraladıktan sonra 4. sırada hangi cismin olduğu aşağıdaki örnekte olduğu gibi görülebilir.
gunes_sistemi.sort_values('Porb').iloc[3]
gunes_sistemi.sort_values('Porb').iloc[1:5,1]
gunes_sistemi.iloc[[4,3,0],-1]
# Belirli bir deger icin
print(gunes_sistemi.loc['venus','Rg'])
print(gunes_sistemi.iloc[-1, -1])
# Bu islemi daha hizli yapabilen iki metot at ve iat metotlaridir
print(gunes_sistemi.at['dunya','Mg'])
print(gunes_sistemi.iat[1,2]) #1: dunya 2: Porb
Tıpkı numpy dizilerinde olduğu gibi pandas veri çerçevelerinde de bir koşula dayanan dilimleemler yapılabilir.
print(gunes_sistemi['Porb'] > 365.25)
print(gunes_sistemi[gunes_sistemi['Mg'] > 10.00]['Rg'])
Bir koşula göre dilimlemelerde dahi numpy tarzı dilimlemeler ve indekslemelerin dahi pandas tarafından sağlanan loc
, iloc
, at
, iat
özellikleri (attribute) kullanılması önerilmektedir.
print(gunes_sistemi.loc[gunes_sistemi['Prot'] < 24.0])
Herhangi bir değerin bir sütunda olup olmadığını belirlemek için isin
fonksiyonu kullanılır.
gunes_sistemi[gunes_sistemi['Porb'].isin([365.2, 88.00])]
print(df)
s1 = [1, 2, 3, 4, 5]
df['E'] = s1
print(df)
Ancak indeksleri vererek yeni bir sütun eklemek daha güvenilir ve tutarlı bir yoldur.
gunes_sistemi['e'] = pd.Series([0.205, 0.007, 0.017, 0.055, 0.094, 0.049, 0.057, 0.046, 0.011, 0.244], \
index = ['merkur','venus', 'dunya', 'ay', 'mars', 'jupiter', 'saturn', 'uranus', 'neptun', 'pluto'])
print(gunes_sistemi)
# g/cm^3 cinsinden hacimleri yeni bir sutuna ekleyelim
gunes_sistemi['V'] = gunes_sistemi['Mg']*1e27 / (4./3*np.pi*(gunes_sistemi['Rg']*1e5)**3)
print(gunes_sistemi)
Şimdi aslında bir cüce gezegen olmasına karşın Ceres'in parametrelerini yeni bir satır olarak veri çerçevemize ekleyelim ama Ceres'in yörünge dönemini ($P_{orb}$) ve dış merkezliliğini ($e$) girmeyi unutmuş olalım. Ayrıca diğer parameterleri de bir miktar "karışık" bir sırada verelim. Bunun yanı sıra Ceres'in yarıçapını da Dünya yarıçapı cinsinden ($R = 0.074 R_{dunya}$) biliyor olalım. Sona satır ya da sütün ekleme işi pandas 1.4.0
öncesinde append
metoduyla yapılabilirken artık bu metod kullanımdan kaldırılmıştır ve pandas.concat
fonksiyonu bu amaçlarla kullanılmaktadır.
ceres = {'Prot' : pd.Series([9.1], index=['ceres']),\
'Rg' : pd.Series([0.074*gunes_sistemi.at['dunya','Rg']], index=['ceres']),\
'Mg': pd.Series([0.94e-3], index=['ceres'])}
gunes_sistemi = pd.concat([gunes_sistemi,pd.DataFrame(ceres)], axis=0, sort=False)
print(gunes_sistemi)
ganymede = {'Mg' : 148.2e-3, 'Rg' : 2631., 'e' : 0.001}
gunes_sistemi = pd.concat([gunes_sistemi,
pd.DataFrame(ganymede, index=['ganymede'])],
sort=False)
print(gunes_sistemi)
İki büyük uydunun (Titan, Triton) parametrelerini daha ekleyelim.
iki_buyuk_uydu = [{'Mg':134.6e-3, 'Rg':2575, 'Prot':382.7, 'e': 0.029}, \
{'Rg':1352.5, 'Prot':-141.0, 'e':0.000, 'Mg':21.5e-3}]
gunes_sistemi = pd.concat([gunes_sistemi,
pd.DataFrame(iki_buyuk_uydu, index=['titan','triton'])],
sort=False)
print(gunes_sistemi)
Şimdi bakınca Ganymede'in dönme dönemini girmediğimizi farketmiş ve girmek istiyor olalım. Uydular için yörünge dönemi gezegenlerinin etrafındaki yörüngelerinin dönemi olduğu için girmemeyi tercih ettik (Ay hariç).
gunes_sistemi.at['ganymede', 'Prot'] = 171.7
print(gunes_sistemi)
Veri çerçevelerinden sütun silmek için del
komutu (zira bir veri çerçevesi değiştirilemez (ing. immutable) bir nesnedir), satır silmek için ise drop
metodu kullanılır.
print(sehirler)
sehirler['Bolge'] = ['Akdeniz', 'Marmara', 'Ic Anadolu', 'Ege', 'Guneydogu Anadolu', 'Marmara']
print(sehirler)
del sehirler['Ozellik']
print(sehirler)
sehirler.drop(4, axis=0)
print(sehirler)
Bu durumu kalıcı hale getirmek için drop
metodunun inplace
anahtarına $True$ değeri atanmalıdır.
sehirler.drop(4, axis=0, inplace=True)
sehirler
Pandas özünde numpy dizileriyle de birlikte çalışmak üzere tasarlanmış (vektörleştirilmiş) bir veri yönetim paketi olduğu için diziler üzerinde çalışabilen ön tanımlı (built-in) ya da kullanıcı tanımlı fonksiyonlar Pandas veri nesneleri üzerinde çallışır.
import pandas as pd
import numpy as np
rstglvr = np.random.RandomState(42)
rstglsr = pd.Series(rstglvr.randint(0, 20, 5))
rstglsr
rstgldf = pd.DataFrame(rstglvr.randint(0, 20, (4, 5)),
columns=['A', 'B', 'C', 'D', 'E'])
rstgldf
print(np.sin(rstgldf/16. + np.pi / 2))
Herhangi bir fonksiyon ya da aritmetik işlem birden fazla seri ya da veri çerçevesi arasında uygulanmak istendiğinde Pandas iki veri nesnesinin indekslerini hizalar.
yuzolcum = pd.Series({'Birlesik Krallik': 242495, 'Fransa': 551695, 'Almanya': 357386,
'Ispanya': 498511, 'Turkiye': 783562}, name='yuzolcum')
nufus = pd.Series({'Turkiye': 84200851, 'Ispanya': 46767543, 'Birlesik Krallik': 67803450,
'Almanya': 83792987, 'Fransa': 65227357}, name='yuzolcum')
nufus_yogunlugu = nufus / yuzolcum
print(nufus_yogunlugu)
gdp = pd.Series({'Almanya': 3863344, 'Fransa': 2707074, 'Italya': 1988636,
'Izlanda': 24280, 'Turkiye': 743708}, name='yuzolcum')
gdp_kisibasi = gdp / nufus
print(gdp_kisibasi*1e6)
Bir veri çerçevesi ile bir seri arasında işlem yaparken işlem varsayılan olarak satırda gerçekleşir ve indeksler hizalanır.
A = rstglvr.randint(20, size=(4, 5))
dfA = pd.DataFrame(A, columns=list('XYZTW'))
dfA - dfA.iloc[0]
İşlem sütun üzerinde yapılmak istendiğinde axis
anahtarı işlemin sütun üzerinden yapılacağını belirtmek üzere $0$ 'a eşitlenir
print(dfA)
dfA.subtract(dfA['Z'], axis=0)
apply
metodu herhangi bir fonksiyonun tüm veriçerçevesi ya da seri üzerine uygulanmasını sağlar.
import pandas as pd
import numpy as np
rstglvr = np.random.RandomState(42)
print(rstglvr)
A = rstglvr.randn(20)
dfA = pd.DataFrame(A.reshape((4,5)), columns=list('XYZTW'))
print(dfA)
print(dfA.apply(np.cumsum))
Bir veri tablosuna ya da pandas diliyle veri çerçevesinde karşılığı bulunmayan bir verinin nasıl girileceği tartışmalı bir konu olup, tek bir çözümü de yoktur. Veri türüne bağlı olarak bazı durumlarda $-1$, $9.9999$, $-99999$ gibi "nöbetçi değer" (ing. sentinel) kullanılabileceği gibi, boş bırakmak, '-', 'NA', 'N/A'gibi belirteçler kullanmak da tercih edilebilmektedir. Bu çözümlerin her birinin avantaj ve dezavantajları bulunur. Örneğin tüm verilerin pozitif olarak girileceği bir sütunda değeri olmayanların yerine $-1$ girmek sütunlar üzerinde matematiksel işlemler yapılırken bu sütunun da dikkate alınması ve CPU'ya bu nedenle ek yük binmesi anlamına gelir.
Pandas, bir veri çerçevesi ya da seride bulunmayacak verilerin yerine $None$ ve $NaN$ nesnelerini kullanır. Ancak $None$ kullanıldığına veri tipi de nesne ($object$) olur ki bu işlemlerin daha kısa sürede yapılmasına olanak sağlayan $NaN$ seçeneğine göre dezavantajlıdır.
for dtype in ['object', 'int']:
print("dtype =", dtype)
%timeit np.arange(1E6, dtype=dtype).sum()
print()
$NaN$ veri türü IEEE kayan noktalı sayı standartları arasında tanımlanmış ve tüm programlama dilleri ve dijital işlemlerde kullanılan bir veri türüdür. $NaN$ veri türü ile yapılan tüm işlemler sonuç döndürür.
x = np.array([1, np.nan, 3, 4])
print("1 + x = ", 1 + x)
print("0x = ", 0*x)
print("ln(x) = ", np.log(x))
print("min(x) = ", np.min(x))
print("nanmax(x) = ", np.nanmax(x))
print("SIGMA(x) = ", np.sum(x))
print("nansum(x) = ", np.nansum(x))
pandas modülünde girilmeyen ya da NaN
(ing. Not a Number) veya None
olarak girilen verileri (Null) yönetmek üzere de özel fonksiyonlar (isnull
, notnull
,dropna
,fillna
) bulunmaktadır.
veri2 = {
'ogrno' : ['08','16','32','74'],
'odev' : [56, 72, 60, 84],
'proje' : [70, 43, 57, 71]
}
ders2 = pd.DataFrame(veri2)
ders2
ders2 = pd.concat([ders2,pd.DataFrame({'ogrno' : 18, 'proje' : 37}, index=[4])],
sort=True)
ders2
ders2 = pd.concat([ders2,pd.DataFrame({'ogrno' : 24, 'proje' : 65}, index=[5])],
sort=True)
ders2
Nan
olan değerlerin tablodaki yerini isna
fonksiyonuyla görebiliriz.
# isna fonksiyonu Null degeri veren elemanlari True
# digerlerini False isaretleyerek bir maske (mask) olusturur
pd.isna(ders2)
dropna
$NaN$ değer bulunan satır (varsayılan) ya da sütunları silmek için kullanılır.
ders2.dropna(subset=['proje'])
Varsayılan davranışında satır silen drop
fonksiyonu NaN
sütunları silmek için de kullanılabilir. $axis = 1$ ya da $axis = columns$ verilerek NaN
barındıran tüm sütunlar silinebilir.
ders2.dropna(axis=1)
dropna
fonksiyonunun how
parametresi en az bir değeri ($how = 'any'$) veya tüm değerleri ($how = 'all'$) NaN
olan, satır veya sütunun veri çerçevesinden kaldırılıp kaldırılmayacağını belirler.
ders2.dropna(how='any')
Bazen $NA$ değerlerini silmek yerine, geçerli bir değerle değiştirmek tercih edilebilir. Bu değer, sıfır gibi tek bir sayı olabilir veya çevredeki değerler kullanılarak yapılabilecek bir interpolasyon sonucu elde edilen değer olabilir. Aslında bu işlem isnull
fonksiyonu ile bir maske (ing. mask) üretilerek $NaN$ değerler belirlendikten sonra istenen değerlerle değiştirilerek de yapılabilir. Ancak bu çok sık kullanılan işlem olduğu için Pandas dizinin bir kopyasını $null$ değerlerle değiştiren fillna
metodunu sağlamıştır.
veri = pd.Series([1, np.nan, 2, None, 3, np.nan], index=list('PQRSTU'))
veri
# NaN degerleri 0 ile degistir
veri.fillna(0)
# Nan degerleri bir sonraki deger ile degistir
veri.fillna(method='ffill')
# Nan degerleri bir onceki deger ile degistir
veri.fillna(method='bfill')
ders2.fillna(0, axis=1)
Pandas modülünden tablo yapısındaki ascii (salt metin), sütunlardaki değerleri virgülle ayrılan (csv), yaygın sayfa programlarıyla oluşturulan pek çok formattaki dosyadan veri çekilip, üzerinde işlemler yapılabilir. Öncelikle sütunlardaki değerleri virgülle ayrılmış bir tablo yapısı (csv) üzerinde bir örnekle çalışalım. ders_notlar.csv dosyasında bir derste öğrencilerin aldığı notlar (iki ödev, bir arasınav ve bir final olmak üzere) verilmiştir. Öncelikle bu dosyadaki veriyi alıp, bir veri çerçevesine aktaralım. Sonrasında buradaki verinin üzerinde işlemler yapabilir ve analiz edebiliriz.
Pandas modülünün csv
fonksiyonu metin değişkenlere aktarılan sütun değerlerinin başında boşluk varsa (whitespace
) bunları da metne dahil ettiğinden bu davranışı değiştirmek üzere skipinitialspace
parametresi True değerine ayarlanmalıdır.
Pandas read_csv
fonksiyonu virgülle ayrılmış olmasalar dahi belirli bir karakterle ayrılmış (delimited) dosyaları veriçerçevelerine dönüştürebilir. Pek çok başka formattaki veri dosyasını da read_excel()
, read_json()
, read_html()
ve read_sql_table()
gibi fonksiyonlar yardımıyla veriçerçevelerine transfer etmek mümkündür. Herhangi bir programdan (excel, emacs, notepad vs.) kopyalanan bir veriyi dahi read_clipboard()
kullanarak veriçerçevesine transfer edebilirsiniz.
import pandas as pd
notlar = pd.read_csv('ders_notlar.csv', index_col="ad", skipinitialspace=True)
notlar
Eğer ders_notlar.csv dosyasını bir metin editörle açacak olursanız hem NaN
olarak girilen değerler (sınava girmeyenler için), hem de boş bırakılan değerler (durum sütununda henüz durumu belirlenmemiş öğrenciler; W: dersten çekilenleri göstermektedir) göreceksiniz. Pandas boş bırakılan değerleri de NaN
olarak yorumlamaktadır. Tüm sütun isimleri veri dosyasından çekilirken ad sütunu indeks olarak işaretlenmiştir. Başlığı olmayan ya da atlanmak istenen veri dosyaları için csv
fonksiyonunun header
parametresinde çeşitli seçenekler tanımlanmıştır.
# indekslenen sutun
print(notlar.index)
print("-----------------")
# tüm sutunlar
print(notlar.columns)
print("-----------------")
# Herhangi bir ogrencinin butun notlari
print(notlar.loc['ogrenci28'])
print("-----------------")
# Tum arasinav notlari
print(notlar['arasinav'])
notlar.rename(index={"ogrenci37":"mehmet"},inplace=False)
Şimdi bir analiz işlemi yapabiliriz. Örneğin aldığı tüm notlardan öğrencinin ağırlıklı genel ortalamasını çıkarabiliriz. Bu ders için her iki ödevin %10'ar (toplamda %20), arasınavın %30, finalin ise %50 ağırlığının olduğunu varsayalım
notlar['genel_ortalama'] = notlar['odev1']*0.1 + notlar['odev2']*0.1 + notlar['arasinav']*0.3 + notlar['final']*0.5
notlar['genel_ortalama']
Bu ortalamalar hesaplanırken önemli bir şansımız arasınava girmeyen tüm öğrencilerin finale de girmemiş olması ve finale girmeyen tüm öğrencilerin de dersten çekilmiş (durum: W) olmalarıdır. Durumun bu olması zorunlu değildir. Örneğin bir öğrenci arasınava girmeyip derse devam etmiş, finale girmiş olabilir. Bu durumda arasınav notunu 0 yapmak gerekir. Bu tür ayarlamalar koşul yapılarıyla (if
- elif
- else
) kontrol edilebilir. Ayrıca pandas bu tür kontroller için pratik fonksiyonlar da sağlamaktadır. Örneğin her öğrenci için ağırlıklı ortalama çıkarmak bakımından arasınav ve finale girmeyenlerin notlarını 0 varsaymak iyi bir fikir olacaktır. Öğrencinin dersten çekilip çekilmediği durum sütununda kontrol edilebildiği için (W) bu önemli bir avantaj sağlayacaktır. Bu amaçla veri çerçeveleri üzerine tanımlı fillna
metodu kullanılabilir.
notlar = notlar.fillna(0)
notlar['genel_ortalama'] = notlar['odev1']*0.1 + notlar['odev2']*0.1 + notlar['arasinav']*0.3 + notlar['final']*0.5
notlar
Bu durumda durum sütununda da W olmayan satırlar 0 ile doldurulmuştur. Ayrıca arasınav ya da finale girmeyen öğrencilerin genel ortalaması NaN
olarak belirlenmiş olduğu için onlar da 0'a dönüştürülmüştür. Bu nedenle genel_ortalama sütununu tekrar hesaplamakta yarar görülmüştür. Şimdi, durum sütunu ortalama notlarına bağlı olarak verilecek olan harf notları ile güncellenebilir.
ogrenciler = notlar.index.values
print(ogrenciler)
for i, ortalama in enumerate(notlar['genel_ortalama']):
if ortalama > 89.500:
notlar.at[ogrenciler[i], 'durum'] = 'A'
notlar
Aynı sonucu dilimleme için koşul kullanmak suretiyle de almak mümkündür. Veri çerçeveleri üzerinde tanımlı loc
metodu herhangi bir veya birden fazla sütunda istenen koşulu sağlayan satırları seçmek için kullanılablir. Bu satırlar seçildikten sonra bu koşulların sağlandığı satırda yer alan istenen sütun(lar) yeni değerleri ile güncellenebilir. Bu noktada birden fazla koşulun and
ya da or
gibi bir bağlaçla bağlanarak kombine koşullar oluşturulması mümkün olmakla birlikte pandas'ın and
ve or
yerine bitwise boolean operatörlerini ( &
ve |
) kullanıyor olmasına dikkat etmek gerekir.
notlar.loc[(notlar['genel_ortalama'] >= 87.500) & (notlar['genel_ortalama'] < 89.500), ['durum']] = 'A-'
notlar.loc[(notlar['genel_ortalama'] >= 84.000) & (notlar['genel_ortalama'] < 87.500), ['durum']] = 'B+'
notlar.loc[(notlar['genel_ortalama'] >= 79.500) & (notlar['genel_ortalama'] < 84.500), ['durum']] = 'B'
notlar.loc[(notlar['genel_ortalama'] >= 76.500) & (notlar['genel_ortalama'] < 79.500), ['durum']] = 'B-'
notlar.loc[(notlar['genel_ortalama'] >= 72.500) & (notlar['genel_ortalama'] < 76.500), ['durum']] = 'C+'
notlar.loc[(notlar['genel_ortalama'] >= 69.500) & (notlar['genel_ortalama'] < 72.500), ['durum']] = 'C'
notlar.loc[(notlar['genel_ortalama'] >= 64.500) & (notlar['genel_ortalama'] < 69.500), ['durum']] = 'C-'
notlar.loc[(notlar['genel_ortalama'] >= 59.500) & (notlar['genel_ortalama'] < 64.500), ['durum']] = 'D+'
notlar.loc[(notlar['genel_ortalama'] >= 54.500) & (notlar['genel_ortalama'] < 59.500), ['durum']] = 'D'
notlar.loc[(notlar['genel_ortalama'] < 54.500) & (notlar['durum'] != 'W'), ['durum']] = 'F'
notlar
pandas'ın istatistiki analize yönelik pek çok fonksiyonu bulunmaktadır. Bunlardan bazılarını örnekleyelim.
notlar.describe()
notlar.sort_values(by="final")
pandas
'ın yeni versiyonlarında istatistiksel işlemler yapılmak istenen sütunlarının bu işleme uygun yapıda olup olmadığı da değerlendirildiğinden, sadece yapılmak istenen uygun sütunların seçilerek ilgili fonksiyon ya da metodun üzerine uygulanması gerekmektedir. Bu nedenle aşağıda ortalaması mean
metoduyla alınmak istenen sütunlar önce select_dtypes(include=np.number)
metoduyla belirlenmiş, sonra mean()
metodu uygulanmıştır.
import numpy as np
notlar.select_dtypes(include=np.number).mean()
notlar[['odev1','odev2','arasinav','final','genel_ortalama']].median()
notlar.select_dtypes(include=np.number).max()
Biraz da analiz yapmak üzere derste alınan notları inceleyelim.
notlar["durum"].value_counts()
Her ne kadar örnekteki veri dosyasyının büyük olmadığından ondan oluşturulan veriçerçevesi üzerindeki işlemler çok hızlı gerçekleşse de string nesneleri işlemleri oldukça yavaşlatır. pandas
string nesneleri gibi nümerik olmayan, heterojen yapıdaki serileri object
nesnesi olarak saklar. Bu veriler hem çok yer kaplar, hem de işlemleri yavaşlatır. Sadece belirli string değerlerini alan object
türündeki veri category
türüne dönüştürülerek hem yerden, hem işlem hızından kazanılabilir.
notlar = notlar.astype({"durum":'category'})
notlar['durum'].dtype
Bu veriçerçevesi kategorik veri taşıdığı için doğrudan hist
fonksiyonunu kullanamasak da bir histogram görmek için plot
fonksiyonunun bar
seçeneğini kullanabiliriz.
from matplotlib import pyplot as plt
notlar['durum'].value_counts().plot(kind='bar')
plt.show()
Veri çerçevesi nesnelerinin üzerinde çizim fonksiyounu (plot
) da tanımlıdır. Bunun için ayrıca matplotlib kütüphanesi çağırmaya gerek yoktur, çünkü çizim fonksiyonu matplotlib'i kendi çağırır.
%matplotlib inline
notlar['final'].plot(marker="o",ls="None")
plt.axhline(y = notlar['final'].mean(), ls="--",color="red")
plt.axhline(y = notlar['final'].median(), ls="--",color="green")
plt.show()
%matplotlib inline
notlar['final'].plot(marker="o",ls="None")
plt.axhline(y = notlar[notlar['final'] != 0.]['final'].mean(), ls="--",color="red")
plt.axhline(y = notlar[notlar['final'] != 0.]['final'].median(), ls="--",color="green")
plt.show()
notlar.plot.scatter(x="arasinav", y="final", marker="o")
plt.show()
pandas veri çerçeveleri kolaylıkla $\LaTeX$ formatındaki tablolara dönüştürülüp, yayınlara aktarılabilir.
notlar_latex = notlar.style.to_latex()
print(notlar_latex)
Sonuç olarak oluşan notlar veriçerçevesini yine virgülle ayrılmış bir salt metin dosyasına (ders_notlar_sonuc.csv) adıyla yazdıralım. df.to_json
gibi fonksiyonlarla dosyalara başka türden formatlarda da yazmak mümkündür.
notlar.to_csv("ders_notlar_sonuc.csv")