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
Verileri pandas
paketiyle işlemeye başlamadan önce, veriyi hazırlamak ve veri yapıları oluşturacak şekilde birleştirmek gerekir. Ayrıca bu işlemler veri biliminin de temel adımlarından birini oluşturur. Böylece daha sonra pandas
kütüphanesi tarafından sağlanan araçlar ile manipüle edilebilirler. Veri hazırlama için farklı prosedürler aşağıda listelenmiştir.
Veri yükleme için çeşitli yöntemlerle (sözlükler, listeler, numpy dizileri kullanarak) pandas serileri (pandas.Series
) ve veriçerçeveleri (pandas.DataFrame
) oluşturmayı Pandas'a Giriş dersinde görmüştünüz. Ayrıca verinin sütunlar şeklinde yapılandırılmış dosyalardan pandas.read_csv
fonksiyonuyla okunarak veriçerçevelerine yüklenmesi de örneklendirilmişti. Ancak, farklı kaynaklarda ve muhtemelen farklı formatlarda saklanan veriyi aldıktan sonra bir veriçerçevesinde (DataFrame'de) birleştirmek gibi hazırlık işlemlerine de ihtiyaç duyulabilir. Örneğin bir ötegezegene ve barınak yıldızına ilişkin pek çok bilgi exoplanet.eu ya da NASA Exoplanet Archive gibi kataloglardan alınabiliyor olsa da özellikle yıldıza ilişkin bilgilerin daha güncel olarak bulunabileceği Gaia veritabanı ya da tayfsal parametrelerinin bulunduğu veritabanlarından alınarak yıldıza ilişkin bazı bilgilerin güncellenmesi ya da diğer bazılarının eklenmesi istenebilir. Bu bölümde verilerin bütünleşik bir veri yapısına dönüştürülmesi için yapılması gerekli işlemler tartışılacaktır.
pandas
nesnelerinde tutulan veriler farklı şekillerde bir araya getirilebilirler:
Veriyi bağlama (ing. merging): pandas.merge()
fonksiyonu, bir veya daha fazla anahtara bağlı olarak bir veritabanı çerçevesindeki (DataFrame'deki) satırları birbirine bağlar. SQL dilindeki $join$ metoduna benzer niteliktedir.
Veriyi ucuca ekleme (ing. concatenating): pandas.concat()
fonksiyonu veri nesnelerini (Series ve DataFrame) bir eksen boyunca ucuca ekler.
Veriyi birleştirme (ing. combining): pandas.DataFrame.combine_first()
fonksiyonu, başka bir yapıdan veri alarak veri yapısındaki eksik değerleri doldurmak ya da güncelleme için bağlamanızı sağlayan bir yöntemdir.
SQL'e aşinalığı olanlar için $JOIN$ işlemine karşılık gelen bu birleştirme işlemi, bir veriçerçevesindeki satırların bir veya daha fazla anahtar kullanarak bağlanmasını sağlar. pandas
fonksiyonlarından merge()
bu amaçla kullanılır. Örnek olarak, iki veriçerçevesi ($DataFrame$) tanımlayalım ve bunları birbirine pandas.merge()
fonksiyonuyla bağlayalım.
import pandas as pd
yldz_mV = pd.DataFrame(\
{'id':['Arcturus','Deneb','Betelgeuse','Spica','Rigel','Merak', 'Vega', 'Altair','Bellatrix'],\
'mV': [-0.05,1.25,0.42,0.97,0.13,2.37,0.03,0.76,1.64]})
yldz_par = pd.DataFrame(\
{'id':['Arcturus','Betelgeuse','Rigel','Deneb','Vega','Fomalhaut','Polaris'],\
'par': [88.83,6.55,3.78,2.31,130.23,129.01,7.54]})
print("Gorsel Parlakliklar:")
print(yldz_mV)
print("Paralakslar:")
print(yldz_par)
print("Baglanmis Vericercevesi")
pd.merge(yldz_mV,yldz_par)
Sonuçtan da görülebileceği gibi, döndürülen veriçerçevesi, iki veriçerçevesinde de bulunan her iki sütunun yalnızca ortak olan yıldız adlarındaki tüm satırlarını içermektedir. Bu durumda merge
fonksiyonu birleştirmenin yapılacağı herhangi bir sütunun adı belirtilmeden kullanılmıştır. Çoğu zaman entegrasyonun hangi sütun üzerinden yapılacağının da belirlenmesi gerekir. Bunun nasıl yapılabileceğine ilişkin seçenekleri görmeden önce iki veriçerçevesinin birbirine ne şekillerde bağlanabileceğine bakalım.
Yukarıda bir örneğini gördüğümüz bu bağlama türü (ing. one-to-one join) en basiti olup bağlanmak istenen iki sütunun tek bir sütuna dönüştürülmesi sırasında doğrudan bire-bir eşleşebilen ve birbirini tekrar etmeyen (ing. duplicate) satırların her iki veriçerçevesinde de bulunan sütunlardaki bilgilerinin de eklenmesiyle yeni bir veriçerçevesinin oluşmasını sağlar.
Yukarıdaki örnek bu şekilde bir birleştirmedir. Her iki veriçerçevesinde de $id$ sütununda yer alan yıldız adları tektir ve birbirlerini karşılar. Daha sonra dıştan bağlama (outer join) işleminde göreceğiniz gibi her iki veriçerçevesindeki yıldızların da bulunduğu bir veriçerçevesi elde edilebilir. Ancak pandas.merge()
fonksiyonunun doğal davranış şekli içten bağlama (inner join), bir başka deyişle "kesişim" almadır. Dolayısıyla yukarıdaki örnekte her iki veriçerçevesinde de bulunan yıldızlar yeni veriçerçevesine alınmıştır.
Bir başka örnek için bu kez her iki veriçerçevesinde de aynı satırların bulunduğu ve yine satırların birbirini tekrar etmediği bir durumu inceleyelim.
gezegen_yorunge = pd.DataFrame(\
{'id':['Merkur','Venus','Dunya','Mars','Jupiter','Saturn', 'Uranus', 'Neptun'],\
'e': [0.205, 0.007, 0.017, 0.094, 0.049, 0.057, 0.046, 0.011],\
'i [derece]': [7.0, 3.4, 0.0, 1.9, 1.3, 2.5, 0.8, 1.8],\
'P [gun]' : [88.0, 224.7, 365.2, 687.0, 4331, 10747, 30589, 59800],\
'psi [derece]': [0.034, 177.4, 23.4, 25.2, 3.1, 26.7, 97.8, 28.3]})
gezegen_fiziksel = pd.DataFrame(\
{'id':['Merkur','Venus','Dunya','Mars','Jupiter','Saturn', 'Uranus', 'Neptun'],\
'Mp [x10^24 kg]': [0.330, 4.87, 5.97, 0.642, 1898, 568, 86.8, 102],\
'Rp [km]': [2439.5, 6052., 6378., 3396., 71492., 60268., 25559., 24764.],\
'rho [g/cm^3]': [5.427, 5.243, 5.514, 3.933, 1.326, 0.687, 1.271, 1.638],\
'Prot [saat]': [1407.6, -5832.5, 23.9, 24.6, 9.9, 10.7, -17.2, 16.1]})
print("Baglanmis Vericercevesi")
pd.merge(gezegen_yorunge,gezegen_fiziksel, on='id')
Bağlanan sütunlardan birinin tekrarlanan (ing. duplicate) satırlar içerdiği bağlama türüdür (ing. many-to-one joins). Bu bağlama türüne bir örnek vermek üzere yeni bir veriçerçevesi tanımlayıp yukarıdaki çerçevelerle bağlayalım.
Aşağıdaki örnekte gezegen_fiziksel veriçerçevesine gezegenlerin türünü göstermek üzere yeni eklenen $tur$ sütununda kayaç, gaz ve buz gezegenler birden fazla kez yer almaktadır. Buna mukabil gezegen_tur veriçerçevesinde bu sütun birer kez tekrarlanan türleri içermektedir.
gezegen_fiziksel['tur'] = ['kayac', 'kayac', 'kayac', 'kayac', 'gaz', 'gaz', 'buz', 'buz']
gezegen_tur = pd.DataFrame(\
{'tur' : ['kayac','gaz','buz'],\
'yer' : ['ic gezegen','dis gezegen','dis gezegen']})
print("Baglanmis Vericercevesi")
pd.merge(gezegen_fiziksel,gezegen_tur, on="tur")
Bağlanan sütunlardan her ikisinde de tekrarlanan (ing. duplicate) satırların içerildiği bağlama türüdür (ing. many-to-many joins). Aşağıdaki örnekte daha önce oluşturduğumuz yldz_mV ve yldz_par_sptype veriçerçevelerine her iki tarafta da tekrarlanan satırlar içeren ve yıldızların tayf türünü gösteren $sptype$ sütunu eklenmiştir. Bu durumda yapılan birleştirme bir many-to-many join işlemidir.
yldz_mV_sptype = pd.DataFrame(\
{'id':['Arcturus','Deneb','Betelgeuse','Spica','Rigel','Merak', 'Vega', 'Altair','Bellatrix'],\
'sptype':['K1.5 III', 'A2 Ia', 'M1 Iab', 'B1 V', 'B8 Ia','A0 V', 'A0 V', 'A7 V', 'B1 V'],\
'mV': [-0.05,1.25,0.42,0.97,0.13,2.37,0.03,0.76,1.64]})
yldz_par_sptype = pd.DataFrame(\
{'id':['Arcturus','Betelgeuse','Rigel','Deneb','Vega','Fomalhaut','Polaris'],\
'sptype':['K1.5 III', 'M1 Iab', 'B8 Ia', 'A2 Ia', 'A0 V', 'A0 V', 'F8 Ib'],\
'par': [88.83,6.55,3.78,2.31,130.23,129.01,7.54]})
print("Gorsel Parlakliklar ve Tayf Turleri:")
print(yldz_mV_sptype)
print("Paralakslar ve Tayf Turleri:")
print(yldz_par_sptype)
print("ID Uzerinden Baglanmis Vericercevesi")
print(pd.merge(yldz_mV_sptype,yldz_par_sptype))
print("Tayf turu Uzerinden Baglanmis Vericercevesi")
print(pd.merge(yldz_mV_sptype,yldz_par_sptype,on="sptype"))
Görüldüğü gibi her iki veri çerçevesinde ortak olan yıldızlar (Arcturus, Deneb, Betelgeuse, Rigel ve Vega) olduğu için iki veriçerçevesi birbirine $ID$ sütunu üzerinden bağlandı. Ancak biz aynı olan tayf türlerinden bağlamak istiyor da olabliirdik. Bunu on
parametresini istenen sütun adına ($sptype$) ayarlayarak yapabiliriz. Her ne kadar bu parametre herhangi bir sütuna verilmediğinde her iki veri setinde aynı olan ilk sütunlar bağlama için seçilse de hangi sütun üzerinden bağlama yapılmak istendiğini her zaman belirtmekte, bir başka deyişle her zaman on
parametresini hangi sütun üzerinden bağlama yapılmak isteniyorsa ona ayarlamakta fayda vardır.
print("Tayf Turu Uzerinden Baglanmis Vericercevesi")
pd.merge(yldz_mV_sptype,yldz_par_sptype, on='sptype')
Görüldüğü gibi her iki verçevesi tayf türü üzerinden bağlandığı zaman yıldız adları farklı olabilmekte, bu durumda da merge
işlemiyle yeni oluşturulan veriçerçevesinde iki ayrı id sütunu oluşmaktadır (id_x ve id_y). İstendiği takdirde sondaki "_x" ve "_y" yerine istenen başka bir metin de kullanılabilir, bunun için suffixes
parametresi istenen metinlere ayarlanır. Özet olarak $merge$ işleminin sonucu hangi kriterle bağlama yapıldığına bağlı olarak değişebilmektedir.
Bununla birlikte, genellikle, bunun tersi bir problemle karşılaşılır: iki farklı veri setinde aynı bilgiyi içeren iki farklı sütun adının olması! Bu durumu gidermek için birinci ve ikinci veriçerçevesi için anahtar sütunu belirten left_on
ve right_on
seçeneklerinin kullanılması gerekir. Öncelikle yldz_mV_sptype veriçerçevesindeki sütun isimlerini değiştirelim.
yldz_mV_sptype.columns = ['yildiz','tayfturu','V']
print(yldz_mV_sptype)
pd.merge(yldz_mV_sptype, yldz_par_sptype, left_on='yildiz', right_on='id')
pd.merge(yldz_mV_sptype, yldz_par_sptype, left_on='tayfturu', right_on='sptype')
Varsayılan olarak, merge
fonksiyonu SQL diliyle bir "inner join" işlemi gerçekleştirir; bir başka deyiişle, sonuçtaki kesişen satırlar alınır. Her iki veriçerçevesinde de bulunmayan satırlar ise kaybedilir. Diğer olası seçenekler "left join", "right join" ve "outer join" 'dir ve how
parametresinin uygun şekilde ayarlanmasıyla kullanılabilir. Bu seçeneklerin nasıl çalıştğını örnekleyerek anlamak için öncelikle yldz_mV_sptype veriçerçevesinin başlıklarını eski haline dönüştürelim.
yldz_mV_sptype.columns = ['id','sptype','mV']
yldz_mV_sptype
pd.merge(yldz_mV_sptype,yldz_par_sptype, on='id')
pd.merge(yldz_mV_sptype,yldz_par_sptype, on='id', how="outer")
Görüldüğü gibi dıştan bağlamada ($outer$) karşılıklı gelemeyen satırlar için sol ve sağ tarafta $NaN$ değerleri oluşmuştur. outer join, özünde bir "bileşke" işlemidir ve her iki veriçerçevesinde ortak olmayan satırlar da alınır. Bu sırasıyla $left$ ve $right$ seçeneklerinin nasıl çalıştığı konusunda fikir verir. Özünde bu her iki seçenek de birer outer join seçeneğidir.
pd.merge(yldz_mV_sptype,yldz_par_sptype, on='id', how="left")
Görüldüğü gibi sol taraftaki (fonksiyona ilk olarak verilen) yldz_mV_sptype veriçerçevesinde bulunan tüm yıldızların bilinen parametreleri entegre çerçevede bulunurken; sağdaki (fonksiyona ikinci olarak geçirilen) yldz_par_sptype veriçerçevesindeki sütunlarda bilgisi olan sadece Vega yıldızının paralaks ve tayf türü bilgileri geçirilmiştir. Sol dış birleştirme (left outer join) kullanıldığında oluşan yeni veriçerçevesinde sol taraftaki (fonksiyona ilk sırada sağlanan) tüm satırlar bulunurken, sağ taraftaki veriçerçevesinde sadece sol taraftaki veriçerçevesinde de bulunan satırlar alınır; eşleşmeyen satırlar ise alınmaz. Ayrıca sütunların sıralamasında da öncelik sol taraftaki veriçerçevesinin sütunlarındadır. Bu durum $right$ seçeneğinde tam ters şekilde çalışır.
pd.merge(yldz_mV_sptype,yldz_par_sptype, on='id', how="right")
Birden fazla anahtar (sütun adı) üzerinden entegrasyon amaçlandığında istenen sütunlar on
parametresine bir liste dahilinde sağlanır.
pd.merge(yldz_mV_sptype,yldz_par_sptype, on=['id','sptype'], how="outer")
yldz_d_sptype = pd.DataFrame(\
{'id':['Arcturus','Betelgeuse','Rigel','Deneb','Vega','Fomalhaut','Polaris'],\
'sptype':['K1.5 III', 'M1 Iab', 'B8 Ia', 'A1 I', 'A0 V', 'A0 V', 'F8 Ib'],\
'd': 1 / (yldz_par_sptype['par']*1e-3)})
yldz_d_sptype
pd.merge(yldz_mV_sptype,yldz_d_sptype,on=['id','sptype'], how="outer")
pd.merge(yldz_mV_sptype,yldz_d_sptype,on=['id','sptype'], how="inner")
Bazı durumlarda, bir veriçerçevesinin sütunları anahtar olarak düşünülmek yerine, indeksleri veriçerçevelerini birbirine bağlamak için kullanılmak istenebilir. Hangi indekslerin dikkate alınacağını belirtmek için, left_index
veya right_index
seçeneklerini etkinleştirmek (True
olarak ayarlamak) gerekir. Bu durumda aynı indekse sahip satırlar birleştirilirken, left_index
soldaki (fonksiyona ilk geçirilen) veriçerçevesinin aynı sütunlarını $x$, sağdaki veriçerçevesinin sütunlarını $y$ ile isimlendirir. Örnekte sağdaki veriçerçevesinin (yldz_par_sptype) 0-6 indeksleri arasında kayıtlar olduğundan bağlanmış veriçerçevesinde de sadece bu indeksler vardır. Soldaki veriçerçevesinin (yldz_mV_sptype) 7 ve 8. indekslerindeki $Altair$ ve $Bellatrix$ bağlanmış veriçerçevesinde yoktur.
pd.merge(yldz_mV_sptype,yldz_par_sptype, how="inner", right_index=True, left_index=True)
DataFrame nesneleri, indeksler üzerinden bağlamak istediğinizde merge()
fonksiyonu altyapısını kullanan ve onun işlevini genişleten join()
metoduna sahiptir. Bu metodun kullanımını örneklemeden önce yine birinci veriçerçevesinin sütun başlıklarını farklı olacak şekilde ayarlayalım.
yldz_mV_sptype.columns = ['yildiz','tayfturu','V']
yldz_mV_sptype
yldz_par_sptype
join
arkaplanda merge
fonksiyonunu kullanır ancak ondan farklı olarak veriçerçevesi ve seriler üzerinde tanımlanmış bir metot yapısındadır. Yukarıdaki gibi bir isim değişikliği yapılmaz ve her iki veriçerçevesinde de aynı sütunlar bulunursa $join$ işleminin yapıldığı tarafa göre sütunların yeniden isimlendirilmesi için lsuffix
ve rsuffix
parametreleri istenen metinlere ayarlanır.
yldz_mV_sptype.join(yldz_par_sptype, how="inner")
Görüldüğü gibi $join$ ile yapılan bağlama indeksler üzerindendir. Bağlama yapılırken kesişim istendiğini belirtmek üzere how
parametresi $inner$ seçeneğine ayarlanmıştır. Bu nedenle her iki veriçerçevesinde de bulunan $0,1,..,6$ numaralı indekslerdeki satırlar her iki veriçerçevesinden de alınarak bir bağlama işlemi yapılmıştır. Eğer bir önceki örnekte olduğu gibi tüm sütun isimleri farklı değil de bazıları aynı olsaydı, bağlama işlemi yaparken sol ve sağ taraftan gerekecek aynı isimli sütunları yeniden isimlendirmek için hangi metnin sırasıyla bu sütun isimlerinin sonuna ekleneceğini belirtmek için lsuffix
ve rsuffix
parametrelerini istenen metinlere ayarlamak gerekecekti.
$join$ ile bağlamaların indeksler üzerinden yapılma zorunluluğu nedeniyle, bağlanacak (ikinci, "other") veriçerçevesinin indeks sütunu değiştirilip, üzerinden bağlama yapılacak (birinci, "caller") veriçerçevesinin bu sütuna karşılık gelen sütun adı on
parametresine verilerek bağlamada istenen sütunların kullanılması sağlanabilir. Bu değişiklik pandas veriçerçeveleri üzerinde tanımlı set_index
metoduyla yapılabilir. Metod değişikliği kalıcı hale getirmeden kullanılabileceği için indesk değişimi de kalıcı olmamış olur.
yldz_mV_sptype.join(yldz_par_sptype.set_index("id"), how="inner", on="yildiz")
Ayrıca bu bağlamada dıştan bağlama ("outer join") (bileşke) işlemi uuygulandığından sol taraftaki veriçerçevesinin bütun satırları alınmış, sağ tarafta sol tarafla eşleşmeyen tüm satır değerleri $NaN$ olarak belirlenmiştir.
yldz_mV_sptype.columns = ['id','sptype','mV']
yldz_mV_sptype.join(yldz_par_sptype, rsuffix="_sag", lsuffix="_sol", how="outer")
Seri halinde veri saklayan diğer tüm Python nesnelerinde olduğu gibi pandas
veriçerçeveleri ve serileri de ucuca eklenebilir. Bunun için pandas.concatenate
fonksiyonu kullanılır.
Önce serilerle örnekleyelim.
import numpy as np
import pandas as pd
seri1 = pd.Series(np.random.rand(4), index=[1,2,3,4])
seri2 = pd.Series(np.random.rand(4), index=[5,6,7,8])
print(pd.concat([seri1,seri2]))
concatenate
fonksiyonu varsayılan olarak $axis = 0$ parametresiyle çalışır ve satır üzerinden ucuca ekleme yapar. axis
parametresi $axis = 1$ ya da $axis = 'col'$ değerlerine ayarlanırsa ucuca ekleme sütun üzerinden yapılır. Sütunda ucuca ekleme sırasında oluşacak her yeni sütuna $0$'dan başlanarak $1, 2, ... $ numaralı indeksler atanır. Satırda aynı olmayan indekslere NaN
değeri (ing. "not a number") verilir.
print(pd.concat([seri1,seri2],axis = 1))
Varsayılan bu davranış şekli join
parametresinin $inner$ değerine ayarlanmasıyla değiştirilebilir. Gerek SQL, gerekse de pandas
'ta inner join özünde bir kesişim işlemidir. Burada dikkat edilmesi gereken bu şekilde (içten) ucuca eklenecek iki serinin indekslerinin aynı olması gereğidir, aksi takdirde bir kesişim işlemi gerçekleşmez. Bu nedenle ilk örnek olarak iki aynı seriyi burada ucuca ekledik.
print(pd.concat([seri1,seri1],axis = 1, join='inner'))
Ucuca eklenen seri ya da veriçerçevelerinin ucuca eklendikleri yerleri belirlemek üzere keys
parametresi ile hiyerarşik bir indeksleme yapılabilir.
print(pd.concat([seri1,seri2], keys=[1,2]))
Dıştan birleştirmelerde ($axis = 1$, "outer join") bu anahtarlar sütun ismi olur. Ucuca ekleme işlemlerinde varsayılan birleştirme (join) metodu "outer join" olduğu için bunu ayrıca how
parametresi üzerinden belirlemek gerekmez.
pd.concat([seri1,seri2], axis = 1, keys=['A','B'])
concatenate
fonksiyonu seri nesnesi ($pd.Series$) üzerinde çalıştığı şekliyle veriçerçevesi ($pd.DataFrame$) nesneleri üzerinde de çalışır. Ancak veriçerçevesi en az iki boyutlu bir nesne olduğu için yapılabilecek işlemlerin karmaşıklığı da daha fazla olabilmektedir.
Yine klasik bir ucuca ekleme işleminin öncelikle varsayılan modda (satırda) nasıl yapıldığına bir örnekle bakalım.
import pandas as pd
import numpy as np
df1 = pd.DataFrame(np.random.rand(9).reshape(3,3), index=[1,2,3], columns=['A','B','C'])
df2 = pd.DataFrame(np.random.rand(9).reshape(3,3), index=[4,5,6], columns=['A','B','C'])
pd.concat([df1, df2], keys=[1,2])
Görüldüğü gibi ucuca eklenen iki veri setinden birincisi ($df1$) ilk üç satırda yer alırken, keys
parametresi ile bu verisetine ikinci bir indeks olarak $1$ değeri atanmıştır. İkinci veri seti birincinin ucuna satırda eklenince, indeksleri $4,5,6$ olduğundan bir sorun çıkmamış, son üç satırda indeksleriyle birlikte yer almıştır. keys
parametresi ile bu verisetine de ikinci bir indeks olarak $2$ değeri atanmıştır.
Aynı veriçerçevelerini axis
parametresini $1$ değerine ayarlayarak bu kez sütunda ucuca getirmeyi deneyelim.
pd.concat([df1, df2], axis=1, keys=[1,2])
Görüldüğü gibi bu kez veriçerçeveleri sütunda ucuca getirilmiş, birinci veriçerçevesi $1,2,3$ indesklerine sahipken, ikinci veriçerçevesinin sahip olduğu $4,5,6$ indekslerine sahip olmadığı için bu indesklere NaN
değeri gelmiş, tersi durum sütunda birincinin ucuna eklenen ikinci veriçerçevesi için de gerçekleşmiştir. Eğer bu iki veriçerçevesi tanımlanırken indeksleri de aynı tanımlanmış olsaydı nasıl bir durumla karşılaşacağımıza bakalım.
import pandas as pd
import numpy as np
df1 = pd.DataFrame(np.random.rand(9).reshape(3,3), index=[1,2,3], columns=['A','B','C'])
df2 = pd.DataFrame(np.random.rand(9).reshape(3,3), index=[1,2,3], columns=['A','B','C'])
pd.concat([df1, df2], keys=['X','Y'])
Satırda ucuca eklemede ortak sütun isimleri ve ikinci veriçerçevesindeki indeksler aynı şekilde korunarak birincinin ucuna eklenmiş ve son üç satıra gelmiştir. Bu durum aynı indekslerden ikişer tane olmasına yol açmaktadır. Bu durumdan keys
parametresi sırasıyla $X$ ve $Y$ değerlerine atanmak suretiyle yapılan çoklu indesklemeyle kaçınabileceği gibi bu durum hiç bir şekilde istenmiyorsa kodun hata vermesi de sağlanabilir. verify_integrity
parametresi bu durumu denetler. Birden fazla satırın aynı indekse sahip olması her zaman istenen bir durum değildir; zira veriçerçevesinin bütünlüğünü ("integrity") bozar. Bu parametre $True$ değerine ayarlandığında ucuca ekleme işlemi aynı indeksten iki tane oluşmasıyla sonuçlanacaksa bu engellenir ve bir hata mesajı üretilebilir.
try:
pd.concat([df1, df2], verify_integrity=True)
except ValueError as e:
print("ValueError:", e)
Ancak bu durumun bir sorun oluşturmayacağı değerlendiriliyorsa aynı indekse sahip istendiği sayıda satır veriçerçevesinde bulunabilir. Bu durum veriçerçevelerinin bir esnekliği olsa da indeksleme, dilimleme ve çağırma işlemlerinde bariz sorunlara yol açacağı gerekçesiyle veriçerçevesinin bütünlüğünü bozar.
pd.concat([df1, df2])
Aynı indeksten birden fazla oluşmasına engel olmak için bir başka çözüm indeksleri bütünüyle gözardı etmek ve indeks yönetimini pandas
'a bırakmaktır. Bunun için ignore_index
parametresi $True$ değerine ayarlanır. Yukarıdaki örnekte olduğu gibi indekslerin önem taşımadığı ve zaten tamsayı olduğu durumlarda bu kolaylıkla uygulanabilir; ancak bu durumun sorun olarak değerlendirilebileceği indekslerin metinler olduğu örneklerle de karşılaşılabilir. Bir örnekle görelim:
pd.concat([df1, df2], ignore_index=True)
Görüldüğü üzere her iki veriçerçevesinin indeksleri göz ardı edilmiş ve 0'dan başlanarak yeni bir indeksleme yapılmıştır.
Bu kez birleştirmeyi sütunda yapmayı deneyelim ve axis
parametresini $'col'$ değerine ayarlayalım.
pd.concat([df1, df2], axis=1)
Görüldüğü üzere aynı isimde sütunların varlığı da bir problem olmamakta sütunlar yanyana eklenebilmektedir. Yine istenirse bu durumdan ignore_index
ya da çoklu indeksleme (keys
parametresi üzerinden) yoluyla kaçınılabileceği gibi kodun hata vermesi de sağlanabilir (verify_integrity = True
).
pd.concat([df1, df2], axis=1, ignore_index=True)
Veriçerçeve ve serilerinin bağlanmasında (ing. merging) gördüğünüz join
metodu, aynı lineer cebir işlemlerine karşılık gelecek şekilde verilerin ucuca eklenmesinde de kullanılabilmektedir. Yukarıdaki örnekte olduğu gibi aynı ismi taşıyan sütunlardaki verilerin doğrudan alta alta yazılması (bileşkesinin alınması) yerine bir kesişiminin de alınması istenebilir. Bu durumun pandas.merge
fonksiyonundaki join
metodununun $inner$ (kesişim) ve $outer$ (bileşke) değerlerine atanmasıyla sağlandğını hatırlıyorsunuz. Ucucua eklemede de join
metodu bu değerlere atanarak amaçlanan şekilde işlemin yapılması sağlanabilir. Birer örnekle görelim.
df1 = pd.DataFrame({'e':[0.205, 0.007, 0.017, 0.094],
'i': [7.0, 3.4, 0.0, 1.9],
'P': [88.0, 224.7, 365.2, 687.0],
'psi': [0.034, 177.4, 23.4, 25.2]},
index=['merkur','venus','dunya','mars'])
df2 = pd.DataFrame({'e':[0.049, 0.057, 0.046, 0.011],
'P': [4331, 10747, 30589, 59800],
'rho': [1.326, 0.687, 1.271, 1.638]},
index=['jupiter','saturn','uranus','neptun'])
print("Ic gezegenler:")
print(df1)
print("----------------")
print("Dıs gezegenler:")
print(df2)
print("---------------------")
print("pd.concat([df1,df2]")
pd.concat([df1,df2], sort=False)
Görüldüğü üzere aksi söylenmediği takdirde yapılan işlem satırları altalta dizerken tüm sütunları kullanmak (outer join) ve bir satırın herhangi bir sütun için verisi yoksa onu NaN
değerine atamaktır. Eğer ucuca ekleme sırasında sadece ortak olan sütunlar kullanılmak isteniyorsa bu davranış join = 'inner'
ile aşılabilir.
print("pd.concat([df1,df2], join='inner')")
print(pd.concat([df1,df2], join='inner', sort=False))
Bir veriçerçevesini (ya da seriyi) diğerinin ucuna eklerken çakışan indeksleri bir kenara bırakıp, çakışmayanlar için ikinciyi birincinin sonuna eklemek üzere birinci nesnenin üzerinde combine_first()
fonksiyonu tanımlanmıştır. Örneklerle nasıl çalıştığını anlayalım.
import pandas as pd
import numpy as np
seri1 = pd.Series(np.random.rand(5),index=[1,2,3,4,5])
print("Seri 1:")
print(seri1)
seri2 = pd.Series(np.random.rand(4),index=[2,4,5,6])
print("Seri 2:")
print(seri2)
print("seri1.combine_first(seri2)")
seri1.combine_first(seri2)
Görüldüğü gibi $1, 2, 3, 4, 5$ indeksleri birinci seriden ($seri1$), $6$ indeksi ise ikinci seriden ($seri2$) alınarak seriler kombine edilmiştir. Bunun tersine bakalım.
print("seri2.combine_first(seri1)")
seri2.combine_first(seri1)
Görüldüğü üzere $2, 4, 5, 6$ indeksleri ikinci seriden alınırken, onda bulunmayan $1$ ve $3$ indeksleri ise birinci seriden alınmıştır. Serilerin belirli bölümleri de dilimleme yoluyla birleştirilebilir. Aşağıdaki örnekte ilk serinin son üç elemanına kadar olan bölümünün ikinci serinin son üç elemanına kadar olan bölümünü "overlap" etmesi, sonrasında serinin geri kalanının $seri2$ elemanları tarafından doldurulması istenmiştir.
print(seri1[:3].combine_first(seri2[:3]))
Veriyi analize hazırlarken sıklıkla başvurulan bir grup başka işlem de sütunlarla satırların yerlerini değiştirme (pivoting) işlemleridir. Bu işlemlerde sütunlar satırlara dönüştürülebildiği (stacking) gibi satırlar da sütunlara dönüştürülebilir (unstacking). Konuyu bir örnekle anlamaya çalışalım.
import pandas as pd
import numpy as np
df = pd.DataFrame(np.array([65,45,70,80,74,90,44,37,62]).reshape(3,3),
index=['ogr1','ogr2','ogr3'],
columns=['odev','vize','final'])
print(df)
Veriçerçeveleri (DataFrame) üzerinde tanımlıstack()
metodunu kullanarak, sütunları satırlar haline dönüştürmek suretiyle birden fazla (hiyerarşik) indeksi bulunan bir seri üretilmesi mümkündür.
df.stack()
df_ogrs = df.stack()
df_ogrs['ogr1']['odev']*0.2 + df_ogrs['ogr1']['vize']*0.3 + df_ogrs['ogr1']['final']*0.5
Bunun tersi işlem de tahmin edilebileceği gibi unstack()
fonksiyonu ile yapılır. Örneğimizde bu işlem satırları toplayıp sütuna dönüştüreceğinden veriçerçevesinin eski halinde görüntülenmesini sağlayacaktır. Bir not olarak veriçerçevesinin değiştirilemez (immutable) bir nesne olduğunu hatırlatmakta fayda vardır. Dolayısıyla yukarıda yaptığımız işlem sadece bir görüntüleme işlemidir ve veriçerçevemiz dönüşmüş değildir. Bu noktaya dikkat çekerek tersi işlem olan unstack()
işlemini örnekleyebilmek üzere stack()
edilmiş veriçerçevisini bir başka veri çerçevesine aktaralım ve unstack()
işlemini bunun sonrasında yapalım.
df2 = df.stack()
print("Sutundan satira donusum (stack):")
print(df2)
print("Satirdan sutuna donusum (unstack):")
df3 = df2.unstack()
print(df3)
İstendiği takdirde unstack()
fonksiyonunu tersi yönde uygulamak suretiyle orjinal veriye dönmek yerine sütun isimlerini satırlara, satırları da sütunlara veriyi doğru şekilde hizalayarak almak da tercih edilebilir. Örneğimizde bu ödev, vize ve final notlarını satırlara, öğrenci isimlerini de sütunlara almaya karşılık gelecektir.
df4 = df2.unstack(0)
print(df4)
Görüldüğü üzere bu tür işlemler, bir öğrenci için ağırlıklı ortalama almak ya da sınıf için belirli bir sınavın (örneğin vizenin) ortalamasını almak gibi işlemleri yaparken esneklik sağlar. Karşılaşılan probleme göre bu tür esneklikler sağlayan fonksiyonların farkında olunarak döngüler yerine bu fonskiyonların kullanılmlası önerilir.
Bu türden (satırda geri döndürme) bir davranış, transpose
ile de sağlanabilir.
df.transpose()
Satır ve sütunların yerdeğiştirme işlemlerinden bir başkası özellikle benzer ya da aynı elemanların bulunduğu veriçerçevelerinde kullanışlı hale gelir. Okuması zor olan bir tablo türü yerine okuması ve diğer tablolarla yukarıda anlatılan yöntemlerle etkileşimi daha kolay olan bir tablo türüne geçmek tercih edilebillir.
tablo1 = pd.DataFrame({ 'iyon':['FeI','FeI','FeI',
'NaI','NaI','NaI',
'CaII','CaII','CaII'],
'liste':['NIST','VALD','Custom',
'NIST','VALD','Custom',
'NIST','VALD','Custom'],
'dalgaboyu': [5292.512, 5292.509,5292.513,
5682.256, 5682.261, 5682.258,
5167.615, 5167.618, 5167.620]})
print(tablo1)
Farklı iyonların farklı çizgi listelerindeki dalgaboyu değerlerini veren bu tabloyu oluşturulduğu haliyle okumak kolay olmayabilir. Ancak birden fazla indeks kullanarak bu tabloyu geniş formda gösterdiğimizde okuması çok daha kolay gelebilir. Bunun için hangi özelliğe göre döndürme işlemi yapılmak isteniyorsa pivot()
foksiyonuna sağlanmak suretiyle istenen döndürme işlemi yapılabilir.
tablo2 = tablo1.pivot(index='iyon',columns='liste')
print(tablo2)
Bu şekilde her bir iyonun hangi listede hangi dalgaboyunda bulunduğunu görmek çok daha kolaylaştı.
Veri hazırlamanın son aşaması sütunların ve satırların gerektiğinde kaldırılmasıdır (removing). Buna ilişkin örnekler Ders 2: Pandas'a Giriş bölümünde görmüştük. Bununla birlikte, veriyi analize hazırlamak için gerekli işlemleri tamamlamak adına burada yeni örnekler verilecektir.
Aşağıda daha önce de tanımladığımız 3 öğrencinin ödev, vize ve final notlarını gösterir veriçerçevesini tekrar tanımlayarak satır ve sütun çıkarma işlemlerini tekrarlamaya yönelik örnekler bulunmaktadır.
import pandas as pd
import numpy as np
df = pd.DataFrame(np.array([65,45,70,80,74,90,44,37,62]).reshape(3,3),
index=['ogr1','ogr2','ogr3'],
columns=['odev','vize','final'])
print(df)
Tıpkı numpy
dizilerinde olduğu gibi del
komutu veriçerçevelerinden de sütun silmek için kullanılabilir. del
sadece veriçerçeveleri üzerinde tanımlı bir metod ya da fonksiyon olmadığı için kullanılır kullanılmaz etkili olur ve başka bir veriçerçevesine ya da değişkene kopyalama gib bir önlem alınmadıysa, geri alınması mümkün değildir.
del df['odev']
print(df)
İstenmeyen bir satırı kaldırmak içinse drop()
metodu kullanılır. drop()
veriçerçeveleri üzerinde tanımlı bir metod olduğu ve veriçerçevesi ($DataFrame$) nesnesi de değiştirilemez (immutable) olduğu için doğrudan etkili olmaz. Eğer satır silme işlemi korunmak isteniyorsa işlem sonrası oluşan veriçerçevesi bir değişkene (eskisini kaybetmek koşuluyla istenirse aynı isimli) alınmalıdır.
df2 = df.drop('ogr3')
print("ogr3 'un olmadıgi liste:")
print(df2)
print("orjinal liste:")
print(df)
Bir pandas
serisi ya da veriçerçevesinde tekrar eden satırları (ya da bir sütun içindeki değerleri) yakalamak için duplicated()
metodu kullanılır. Metod, tekrarlayan değerlerin arandığı yerdeki bu tür değerleri True
diğerlerini ise False
olarak işaretlemek suretiyle bir şablon (mask) oluşturur. Tıpkı belirli koşullara dayalı olarak o koşulu sağlayan ve sağlamayan satırların filtrelenmesinde olduğu gibi, bu maske ilgilenilen veri setinin filtrelenmesinde kullanılabilir.
Örnek olarak çeşitli tekrar eden satır ve sütunlar barındıran çizgi listesi örneğimize geri dönelim. Bu veriçerçevesinde kendini tekrar eden bir satır olmadığı için tüm satırlar $False$ olarak işaretlenecektir.
import pandas as pd
cizgi_listesi = pd.DataFrame({ 'iyon':['FeI','FeI','FeI',
'NaI','NaI','NaI',
'CaII','CaII','CaII'],
'liste':['NIST','VALD','Custom',
'NIST','VALD','Custom',
'NIST','VALD','Custom'],
'dalgaboyu': [5292.512, 5292.509,5292.513,
5682.256, 5682.261, 5682.258,
5167.615, 5167.618, 5167.620]})
cizgi_listesi.duplicated()
Ancak istersek herhangi bir sütunda tekrar eden değerleri de duplicated()
fonksiyonuyla bulabiliriz.
cizgi_listesi['iyon'].duplicated()
Bu şekilde oluşturduğumuz şablonu daha sonra veriçerçevesine vererek tekrarlayan satırları alabiliriz. Örneğimizde $iyon$ sütununda her bir iyon için verilen ilk satır $False$ değeri ile maskelenmiş iken, daha sonra bu iyona ilişkin diğer çizgi listesindeki değerlerin bulunduğu satırlar $True$ olarak işaretlenmiştir. Dolayısıyla bu şablon cizgi_listesi veriçerçevesine sağlandığında sadece tekrar eden satırlar filtrelenip görüntülenir.
cizgi_listesi[cizgi_listesi['iyon'].duplicated()]
İstendiği takdirde bu tekrar satırları veriçerçevesinden çıkarılabilir. drop_duplicates()
metodu bu amaçla kullanılır.
cizgi_listesi.drop_duplicates('iyon')
Veriçerçevesi üzerinde yaptığımız bu tür filtre işlemlerinin veriçerçevesini değiştirmediğini, işlemin sonucunu saklanması istendiğinde yeni bir veriçerçevesine alınması gerektiğini unutmayınız. Filtre işleminin sonucunun saklanması istendiğinde inplace
parametresinin $False$ olan varsayılan değeri True
ile değiştirilebilir. Bu durumda yapılan değişiklik kalıcı hale getirilmiş olur. drop_duplicates()
tekrarlanan satırların her seferinde sadece ilkinin ($'first'$) korunması yerine sonuncusunun korunmasını ($'last'$) ya da tamamının ($False$) filtrelenmesine de keep
parametresi üzerinden olanak sağlar.
cizgi_listesi.drop_duplicates('iyon', keep='last')
Gördüğünüz gibi sadece kullanıcının oluşturduğu "Custom" isimli çizgi listesindeki benzersiz (unique) satırlar kalmıştır; zira bu çizgi listesi son sıradadır.
Veriçerçeveleri üzerinde çalışırken zaman zaman belirli bir değere karşılık gelen tüm değerleri değiştirmek (ing. replace), değerleri eşleştirerek yeni bir sütun oluşturmak (ing. map) ya da indeks değerlerini istenen değerlerle değiştirmek (ing. rename) istenebilir. pandas
tümü özünde "eşleştirme" (ing. mapping) olan bu işlemler için çeşitli fonksiyonlar sunar.
Bir veriçerçevesindeki değerlerden bazılarını istenen başka bir değerle değiştirmek için veri nesneleri üzerinde tanımlı replace()
metodu kullanılır. Değiştirilmek istenen değeri anahtar (key) yerine koymak istenen değeri değer (value) kabul eden bir sözlük kullanmak bu tür değişimler için ideal bir çözümdür.
Aşağıdaki örnekle bu tür işlemlerin nasıl yapıldığını görelim. cizgi_listesi veriçerçevemizde kullanıcı tanımlı çizgi listesinde yer alan tüm çizgiler için $Custom$ yerine $User$ değerini koymak istiyor olalım ve bu değişimi kalıcı hale getirmek için inplace
parametresini de $True$ değerine ayarlayalım.
import pandas as pd
cizgi_listesi = pd.DataFrame({ 'iyon':['FeI','FeI','FeI',
'NaI','NaI','NaI',
'CaII','CaII','CaII'],
'liste':['NIST','VALD','Custom',
'NIST','VALD','Custom',
'NIST','VALD','Custom'],
'dalgaboyu': [5292.512, 5292.509,5292.513,
5682.256, 5682.261, 5682.258,
5167.615, 5167.618, 5167.620]})
cizgi_listesi
listeadi = {'Custom':'User'}
cizgi_listesi.replace(listeadi, inplace=True)
cizgi_listesi
replace()
fonksiyonu NaN
gibi değerleri 0'la değiştirmek için sıklıkla kullanılır.
import numpy as np
seri = pd.Series([10,2.3,np.nan,-4,0.6,np.nan,7.5])
print(seri)
seri.replace(np.nan,0, inplace=True)
print("Degisiklik sonrasi:")
print(seri)
Bir sütunda verilen değerleri kullanarak yeni bir sütun eklemek de sıklıkla ihtiyaç duyulan bir yöntemdir. pandas
veri nesneleri üzerinde tanımlı map()
metodu kullanılarak bu amaca erişilebilir.
liste_referanslari = {'VALD':'Ryabchikova (1997)', 'NIST':'NIST ASD Team (2019)', 'User':'this study'}
cizgi_listesi['referanslar'] = cizgi_listesi['liste'].map(liste_referanslari)
cizgi_listesi
İndeks isimlerini yenileriyle değiştirmek istendiğinde de aynı yönteme başvurulabilir. Bu kez kullanılması gereken fonksiyon yine veri nesneleri üzerinde tanımlı rename()
metodudur.
Yine cizgi_listesi veriçerçevemiz üzerinde bir örnekle görelim. Yeni indekslerimizi $iyon$ sütunundaki iyon isimleriyle $liste$ sütunundaki liste isimlerini "_" ile birleştirerek oluşturalım.
yeni_indeksler = {}
for i in range(len(cizgi_listesi.index)):
yeni_indeksler[i] = cizgi_listesi.at[i,'iyon']+"_"+cizgi_listesi.at[i,'liste']
print(yeni_indeksler)
cizgi_listesi.rename(yeni_indeksler)
Sütun isimleri de aynı şekilde değiştirilebilir. Bu kez columns
parametresi değişiklik yapılmak istenen sütun isimlerinin bulunduğu sözlüğe ayarlanır.
sutunlar = {'dalgaboyu':'wavelength',
'iyon':'ion',
'liste':'linelist',
'referanslar':'reference'}
cizgi_listesi.rename(columns=sutunlar)
Yine aynı şekilde bu değişikliklerin kalıcı hale getirilmek istendiği durumlarda inplace
parametresine başvurulur.
Serilerde birden, veriçerçevelerinde ikiden fazla boyutun istenmesi durumunda iyi bir çözüm çoklu indeksleme (ing. Multi-indexing) kullanmaktır.
df = pd.DataFrame(np.random.rand(4, 2),
index=[['x', 'x', 'y', 'y'], [0, 1, 0, 1]],
columns=['veri1', 'veri2'])
df
Çoklu indeksler oluşturmak için bir başka yöntem $MultiIndex$ fonksiyonununu kullanmaktır. Demet değişkenler şeklinde tanımlanmış çoklu indeskler için aşağıdaki örnek verilebilir.
index = [('HAT-P-36b', 'T100'), ('HAT-P-36b', "T80"),
('XO-3b', "T100"), ('XO-3b', "T80"),
('KELT-16b', "T100"), ('KELT-16b', "T80")]
derinlikler = [0.0206, 0.0220,
0.0096, 0.0100,
0.0152, 0.0170]
index = pd.MultiIndex.from_tuples(index)
df = pd.DataFrame({"derinlikler":derinlikler}, index=index)
df
Bu durumda 2 boyutlu tablo, 2 katlı indeksleme ((x,y),(0,1)) ile 3 boyuta çıkarılmış olur. İstendiğinde "unstack" edilerek bu tablo tekrar 2 boyutlu bir tabloya aşağıdaki şekilde dönüştürülebiir.
df_2D = df.unstack()
df_2D
Tekrar 3-boyuta $stack$ fonksiyonuyla dönülür.
df_2D.stack()
Bu indekslere isim verilip daha da kullanışlı hale getirilebilir.
df.index.names = ["gezegen","teleskop"]
df
İndeksleme ve dilimleme n-boyutlu numpy
dizilerindekine benzerdir.
df.index
df.loc["HAT-P-36b"]
df.loc["HAT-P-36b","T80"]
Veri işlemenenin son aşamasını veri gruplama (ing. data aggregation) teknikleri oluşturur. Veri gruplama kavramıyla amaçlanan bir dizi veriden tek bir gruba dönüşüm yapmaktır. Örneğin $sum()$, $mean()$, $count()$, $min()$, $max()$ gibi numpy
fonksiyonlarıyla yapılan da bir çeşit gruplamadır. Tüm bu fonksiyonlar bir grup veriyi tek bir sayıyla ifade eder. Sayısal bu tür gruplamaların yanı sıra doğal olarak, aynı türden olduğu düşünülen verilerin, tek bir grupla ifade edildiği kategorik gruplamalar da düşünülebilir.
Öncelikle pandas
'ta bu tür sayısal gruplamaların nasıl yapıldığını ötegezegenler veritabanı üzerinden örnekleyelim. Yine NASA Exoplanet Archive 'dan indirebileceğiniz keşifleri onaylanmış (ing. confirmed) ötegezegenler veritabanı üzerinde çalışalım.
import pandas as pd
import numpy as np
og = pd.read_csv("veri/PSCompPars_2024.04.02_02.08.30.csv", comment="#", skipinitialspace=True,
index_col="pl_name")
og.head()
Öncelikle geçiş yapan ötegezegenlerin yörüngelerinin dış merkezliliklerinin ortalamasını bilmek istiyor olalım.
print("Otegezegenlerin ortalama dis merkezliligi: {:.2f} (+{:.2f} , {:.2f})".
format(og['pl_orbeccen'].mean(),og['pl_orbeccenerr1'].mean(),og['pl_orbeccenerr2'].mean()))
Bir başka örnek olarak istenen sütunların genel istatistikleri için değeri NaN
olan satırları da eledikten sonra describe()
metodunu kullanalım.
og[['pl_bmassj','pl_radj']].dropna().describe()
Gruplama işlemi özünde üç aşamalı bir işlemdir. Öncelikle veri istenen sütundaki eş değerlere göre gruplara bölünür (ing. split), daha sonra istenen gruplama işlemi (toplama, sayma vs.) her bir gruba uygulanır (ing. apply) ve daha sonra gruplar birleştirilir (ing. combine). Programcılıkta da veritabanı yönetiminde de bir amaca ulaşmanın birden çok yöntemi vardır. Burada tarif edilen bu işlemler dizisine ayrı ayrı fonksiyonlar (koşula göle maskeleme, fonksiyona (sum, mean gibi) maskelenmiş veri nesnelerini gönderme, sonra birleştirme (merge, join ya da concat gibi fonksiyonlarla) fonksiyonları kullanılarak gerçekleştirilebilir. Bu fonksiyonların bir sıra dahilinde planlanmasıyla da amaçlanan sonuca erişilebilir. İyi programcılık, programlama dilinin sağladığı olanakların farkında olup bunları gerektiği zaman gereğince kullanmaya dayanır. pandas
veri nesneleri üzerinde tanımlı GroupBy
metodu bütun bu adımları sırayla gerçekleştirebilir.
Örneğin ötegezegenler ($og$) veriçerçevemizi keşif yöntemine göre gruplayalım. Bunun için öncelikle veriçerçevesinin sütunlarının adlarını görüntüleyip, keşif yönteminin hangi sütun isminde yer aldığını bulmalıyız.
og.columns
Görüldüğü gibi keşif yöntemi pl_discmethod başlıklı sütunda yer almaktadır.
og.groupby("discoverymethod")
GroupBy
metodunun sonucu bir DataFrameGroupBy
nesnesidir. Bu nesne ilgilenilen veriçerçevesinin özel bir görüntüsüdür. Bu nesnenin üzerine istenen işlem uygulanarak (ing. apply) gruplama işleminin ikinci aşaması da gerçekleştirilebliir.
og.groupby("discoverymethod").count()
Görüldüğü gibi aslında bir DataFrameGroupBy
nesnesi de bir veriçerçevesi gibi kullanılabilir. Veriçerçevelerinden sütun seçilebildiği gibi bu nesneden de istenen sütunlar seçilebilir ve üzerinde istenen işlemler düşünülebliir. Örneğin daha önce elde ettiğimiz ötegezegenlerin ortalama dış merkezliliğini keşif yöntemine göre ayrı ayrı elde edebilir, karşılaştırmalar yapabilir ve bunlardan istatistiki çıkarımlara yönebiliriz.
og.groupby("discoverymethod")['pl_orbeccen'].mean()
Görüldüğü üzere geçiş ($e_{ort} \sim 0.033$), pulsar zamanlaması ($e_{ort} \sim 0.075$), pulsasyon frekansı değişimi ($e_{ort} \sim 0.075$) gibi yöntemlerde yörünge dış merkezliliği küçük gezegenler keşfedilirken; astrometri ($e_{ort} \sim 0.548$), doğrudan görüntüleme ($e_{ort} \sim 0.393$), dikine hız ($e_{ort} \sim 0.220$) gibi yöntemlerde ise ortalama yörünge dış merkezliliği daha büyük olan ötegezegenler keşfedilebilmektedir. Benzer şekilde yörünge dönemlerini de görmek isteyebiliriz. Bu kez grupladıktan sonra medyan alalım.
og.groupby("discoverymethod")['pl_orbper'].median()
Farklı tekniklerin farklı yörünge karakteristiklerine sahip ötegezegenleri keşfetmeye daha duyarlı oldukları, bu anlamda birbirlerinin keşif yanlılıklarını dengeleme kapasiteleri görülebilmektedir. Örneğin geçiş yöntemi yörünge dönemi küçük ($P_{orb,medyan} \sim 8.49$ gün) gezegenleri keşfetmeye yanlı iken doğrudan görüntüleme tekniğinin yanlılığı tam ters yöndedir ($P_{orb,medyan} \sim 37000$ gün). Her ne kadar doğrudan görüntüleme yöntemiyle daha az gezegen keşfediliyor olsa da geçiş yönteminin üzerinden çıkarımlar yapılmak istenen ötegezegenler popülasyonunu dengelemesi açısından önemi açıktır.
DataFrameGroupBy
nesnesi tıpkı liste (list
), demet değişken (tuple
), sözlük (dictionary
) ya da numpy dizileri (numpy.array
) gibi üzerinde iteratif işlemler uygulanabilen bir nesnedir.
for (teknik, grup) in og.groupby('discoverymethod'):
print("{0:30s} shape={1}".format(teknik, grup.shape))
og['discoverymethod'].value_counts()
Görüldüğü üzere her bir teknikten kaçar gezegen keşfedildiği ve her gruba karşılık gelen teknikle keşfedilen gezegenlerin kaç sütunda (71) parametrelerinin bulunduğu tek tek ekrana getirilebilmektedir. Her bir tekniğin en başarıyla uygulandığı gözlemevi ve teleskoplara ilişkin istatistiki bilgi edinmek için aşağıdaki gibi bir gruplama ifadesi kullanılabilir.
og.groupby('discoverymethod')['disc_facility'].describe()
Her bir yöntemle keşif yapan gözlemevi ya da teleskopların sayısı, en çok keşif yapan gözlemevi / teleskoplar ve kaçar keşif yaptıkları gibi bilgilere describe()
metoduyla ulaşılır. Bu istatistikler için kullanılan gözlemevi / teleskop bilgisi içeren disc_facility
sütunu sayısal olmayan kategorik veri içerdiği gerekçesiyle elde edilen istatistiksel parametreler de ($count$, $unique$, $top$, $freq$) kategorik veriler için geçerli istatistiki parametrelerdir. Daha önceki örneklerden sayısal veri içeren sütunlar söz konusu olduğunda bu istatistiklerin de farklı olduğunu bilyorsunuz. Aşağıdaki örnekte maksimum yörünge büyüklüğü Astronomi Birimi'nde farklı keşif tekniği grupları için farklı istatistikler dahilinde sunulmaktadır. unstack
metodu her bir istatistiği bir diğerinden ayırarak keşif gruplarına birer ikincil indeks haline getirmektedir.
og.groupby('discoverymethod')['pl_orbsmax'].describe().unstack()
Gördüğünüz tüm bu gruplama işlemlerinde sırasıyla gruplara ayırma (ing. split), işlem yapma (ing. apply) ve birleştirme (ing. combine) işlemleri DataFrameGroupBy
nesneleri üzerinde tanımlı groupby()
fonksiyonuyla sırasıyla gerçekeştirilmektedir. DataFrameGroupBy
nesnneleri üzerinde birleştirme (combine) işleminin yanı sıra filtreleme (filter()
), dönüştürme (transform()
) ve fonksiyon uygulama (apply()
) işlemleri de tanımlıdır.
DataFrameGroupBy
nesneleri üzerinde tanımlı bu işlemlerin nasıl çalıştığını anlamaya aggregate()
metoduyla başlayalım. Bu metod istenen liste, metin (fonksiyon adı) ya da sözlük gibi bir yapıda gönderilen tüm fonksiyonları ilgili gruplara uygular.
NASA Exoplanet Archive ötegezegenler veritabanındaki ötegezegenleri keşif yöntemlerine göre gruplandırıp bu kez bazı fiziksel özelliklerinin (kütle, yarıçap, yoğunluk) minimum, medyan ve maksimum değerlerine bakalım. Fonksiyonun gruplandırma dahilinde birden fazla fonksiyonu, birden fazla sütuna aynı anda uygulayabildiğine ilişkin bu örnek aşağıdaki ifadeyle verilebilir.
import numpy as np
og.groupby('discoverymethod')[['pl_bmassj','pl_radj']].aggregate(['min', 'median', 'max'])
DataFrameGroupBy
nesneleri üzerinde tanımlı filter()
metoduyla yapılan filtreleme işlemi, grup özelliklerine göre veri atmanıza olanak sağlar.
Aşağıdaki örneği inceleyelerek filter()
metodunun nasıl çalıştığını anlamaya çalışalım.
import numpy as np
rng = np.random.RandomState(0)
df = pd.DataFrame({'kategori': ['A', 'B', 'C', 'A', 'B', 'C'],
'veri1': range(6),
'veri2': rng.randint(0, 10, 6)},
columns = ['kategori', 'veri1', 'veri2'])
print(df)
Örnek olarak bu veri çerçevesinde olup ikinci veri setinde ($veri2$) standart sapması 4'ten büyük olan ölçümleri filtreleyebiliriz. Bunun için öncelikle bir filtre fonksiyonu tanımlayalım.
filtre_fonksiyonu = lambda x: x['veri2'].std() < 4
print(df.groupby('kategori').filter(filtre_fonksiyonu))
Görüldüğü gibi standart sapması 4'ten küçük olan tek grup A kategorisindeki ölçümlerdir. Tüm standart sapma değerlerine bakarak bunu doğrulayabiliriz. Filtre fonksiyonu (filtering function) bir grubun (burada $A$, $B$ ve $C$ kategorileri) sağlaması beklenen şartı tanımladığı için mutlaka Boolean ($True$ ya da $False$) döndüren bir fonksiyon olmaldıır. filter
fonksiyonu bu şartı sağlayan grupları alır, sağlamayanları eler.
print(df.groupby('kategori').std())
Şimdi bu örnekle öğrendiklerimizi yine ötegezegenler ($og$) veriçerçevemize uygulayarak pekiştirelim. Veriçerçevimizi gruplama için yine discoverymethod sütununda verilen keşif yöntemini kullanalım. Sonrasında filtrelemeyi keşfettiği gezegenlerin yörüngelerinin ortalama dış merkezliliği 0.25'ten büyük olan tekniklerle ($e_{ort} > 0.25$) keşfedilen gezegenleri göstermek üzere yapalım. Bu gezegenlerin de tüm sütunlardaki değerlerini değil sadece keşif yöntemi (discoverymethod), yörünge dış merkezliliği (pl_orbeccen), yörünge dönemi (pl_orbper) ve yörünge büyüklüğünü (pl_orbsmax) veren sütunları gösterelim.
filtre_fonksiyonu = lambda x: x['pl_orbeccen'].mean() > 0.25
print(og.groupby('discoverymethod').\
filter(filtre_fonksiyonu)[['discoverymethod','pl_orbeccen','pl_orbper','pl_orbsmax']].\
groupby('discoverymethod').mean())
Görüldüğü gibi $e_{ort} > 0.25$ koşulunu sağlayan iki yöntem doğrudan görüntüleme (ing. Imaging) ve astrometri yöntemleridir. Ekrana bu yöntemlerle keşfedilen gezegenlerin istenen parametreleri, DataFrameGroupBy
nesnesi üzerinde tanımlı filter()
fonksiyonunun sonucu bir veriçerçevesi türünde nesne olduğu için getirilebilmiştir.
Sonuç olarak filter()
fonksiyonu uygulandığı veriçerçevesinin bir şekilde sınırlandırılmış (kısaltılmıış) bir versiyonunu döndürür.
Filtreleme verilen veri gruplarını bir filtre fonksiyonuna tabi tutup, bu filtre fonksiyonuyla belirlenen şartı sağlayanlarını alır ve gerisini filtreleyip elerken, transformation()
metodu grubu ilgili fonksiyon çerçevesinde dönüştürür.
Örneğin bir veri setindeki her bir veriden o verinin dahil olduğu grubun (örneğin $A$ kategorisinin) ortalamasını çıkararak ilgili sütun ve satıra yazarak veriçerçevesini dönüştürmek (transform etmek) için aşağıdaki gibi bir ifade kullanılabilir. Yine bu işlemin sonucunun yeni bir veriçerçevesi tanımladığını ve saklanmak isteniyorsa bu veriçerçevesine bir isim verilmesi gerektiğini unutmayınız.
import numpy as np
rng = np.random.RandomState(0)
df = pd.DataFrame({'kategori': ['A', 'B', 'C', 'A', 'B', 'C'],
'veri1': range(6),
'veri2': rng.randint(0, 10, 6)},
columns = ['kategori', 'veri1', 'veri2'])
print(df)
donusum_fonksiyonu = lambda x: x - x.mean()
df.groupby('kategori').transform(donusum_fonksiyonu)
Yöntemi öntegezegen veritabanı üzerinde, her bir yöntemin keşfettiği gezegenlerin parametrelerini o yöntemle bu parametre için keşfedilen gezegenlerin ortalama değerine normalize ederek (bölerek) vermek için kullanalım ve sonucu yörünge dönemi (pl_orbper) için görüntüleyelim.
def donusum_fonksiyonu(x):
return x / x.mean()
og.groupby('discoverymethod')[['pl_orbeccen','pl_orbper','pl_bmassj']].transform(donusum_fonksiyonu)['pl_orbper']
apply()
metodu herhangi bir fonksiyonun bir DataFrameGroupBy
nesnesine uygulanmasını sağlar. Her bir grubun birinci veri setindeki ölçümlerini ($veri1$) ikinci veri setindeki ölçümlerin toplamına ($veri2$) normalize etmek istiyor olalım. Aşağıdaki $normalizasyon$ fonksiyonunun df.groupby('kategori')
ifadesi ile elde edilen DataFrameGroupBy
nesnesine uygulanması sonucu $veri1$ sütunundaki veri seti her bir grup için o grubun $veri2$ veri setindeki ölçümlerin toplamına bölünmesi (normalize edilmesi) sağlanmıştır.
def normalizasyon(x):
x['veri1'] /= x['veri2'].sum()
return(x)
print(df.groupby('kategori').apply(normalizasyon))
apply()
fonksiyonu oldukça kullanışlı bir fonksiyon olup, herhangi bir fonksiyonun sadece DataFrameGroupBy
nesnelerine değil, istenen tüm veriçerçevelerine uygulanmasını sağlar. Sonuç istenirse bir pandas
veri nesnesi istenirse de skaler bir değer olabilir.
Bir önceki bölümde transformation()
metoduyla bütun sütunlara uyguladığımız normalizasyonu, sadece yörünge dönemleri bilinen tüm gezegenlerin yörünge dönemlerinin, keşfedildikleri yöntemle keşfedilen tüm gezegenlerin yörünge dönemi ortalamasına oranını hesaplamak üzere kullanabiliriz.
def normalizedonem(x):
return x['pl_orbper'] / x['pl_orbper'].mean()
og.groupby('discoverymethod').apply(normalizedonem)