Python İle Temel Veri İşlemleri
Örneklerle String ve Pandas DataFrame Metodları
Bir içerik sitesi, hedeflediği kitle ile (ilgili aramaları yapan yeni kullanıcılar) ilk tanıştığı ilk an itibariyle 2 aşamalı bir alt hedeflemeye sahip olur; yeni ziyaretçilerin artışını sağlamak ve yeni ziyaretçilerin yeniden ziyaret etmesini (geri gelen) sağlamak. Bu alt hedefler de yeni içerik üretimi ve var olan içeriklerin düzenli olarak geliştirilmesini zorunlu kılar.
İçeriklerin düzenli olarak takibi ve gerekli optimizasyon - geliştirme süreçleri sistemli bir şekilde ele alınamazsa zaman içerisinde içerikler 3 gruba ayrılmaya başlar; öne çıkan içerikler, ziyaretçisi olmayan içerikler, aradakiler. Özellikle teknik problemler ve/veya algoritma güncellemelerinde öne çıkan içeriklerin etkilenmesi istenen bir durum değildir. Çünkü, değişim sitenin ortalama sayfa görüntüleme sayısını da önemli düzeyde etkiler. Ziyaretçisi olmayan içerikler ise sadece yük oluşturur. Aradaki içerikler ise rekabet içerisinde (site içi ve site dışı) pozitif ve negatif küçük değişimler gösterseler de çoğu zaman uzun kuyruklu arama terimleri (long-tail keywords) ile ilişkilidir.
Bir içerik yayıncı, içerikleri düzenli olarak içerik-ziyaretçi ve içerik-arama motoru bağlamlarında değerlendirmeli ve gerekli ise optimize etmelidir. Yazı içerisindeki bağlantılar zaman içerisinde işlevsiz hale gelmiş olabilir, görseller ve sayfa içerisindeki JavaScript kodları ile ilgili problemler oraya çıkmış olabilir, vb.
Bu yazıda, Python İle Google Analytics ve Search Console API İşlemleri başlıklı bir önceki yazının devamı olarak Python programlama dili çerçevesinde sunulan Pandas kütüphanesini ve birkaç string metodunu kullanarak içerikler hakkında nasıl fikir edinebileceğimize değinmeye çalışacağım. Mümkün olduğu kadar pratik yapabilmek adına Pandas metodlarını kullanmaya öze göstereceğim. Zaman içerisinde bazı bölümlerde daha işlevsel ve uygun çözümler bulabilirsel kod değişikliğine gidebilirim. Bu durumda, eski çözümleri yorum olarak tutmaya özen göstereceğim.
Python İle Google Analytics ve Search Console API İşlemleri başlıklı yazıda Google Analytics Reporting API1 aracılığı ile belirli bir tarih arasında görünülenmiş olan sayfalar ile ilgili bazı bilgiler edinmiştik. Burada görüntülenmiş ifadesi Google Analytics izleme kodunun çalıştığı ve Google Analytics sunucusuna sayfa ile ilgili verilerin iletildiği süreci ifade eder. Sayfalar ön tanımlı olarak URL temelinde ele alınır2.
document.title
document.location
Elbette JavaScript ve/veya measurement protocol ile farklı veya değiştirilmiş bilgiler de Google sunucularına iletilebilir.

Bu bilgilerin yanı sıra sosyal medya platformları, reklam mecraları ve e-posta bültenler gibi farklı kaynaklar URL'e çeşitli sorgu parametreleri (query) ekleyeceklerdir veya farklı ülkelerden erişim sağlayan ziyaretçiler Google Translate ve benzeri araçlarla sayfa başlığı ve içeriğini farklı dillere çevirebilirler. Bu ve benzeri pek çok neden sayfalara dair aykırı değerler oluşturacağı için bir ön işlem yapılması gerekliliğini doğurur.

Öncelikle, veriler ile ilgili ön bir işlem olarak ilgili metrik ve boyutların yer aldığı ve istediğimizde hızlıca erişebileceğimi özelleştirilmiş bir Google Analyics raporu oluşuralım.

Pandas ve String Metodları İle Verilerin Düzenlenmesi
Aşağıdaki işlemlerde pratik yapmak adına mümkün olduğunda Python Pandas metodlarını kullanmaya özen göstereceğim. Kimi durumlarda da string metotları yazısı ile ilişkili olarak ek örneklere yer vereceğim. O halde, adım adım başlayalım.
Kullanacağımız veri seti Python İle Google Analytics ve Search Console API İşlemleri başlıklı yazıda oluşturduğumuz Google Analytics sayfa görüntüleme verileri. Ana kütle boyutumuz 5939. Ancak, sitemap üzerinden bakıldığında beklenen sayının 560 civarında olması gerekirdi. Peki, bu farklılığa dair neler söyleyebiliriz?
import pandas as pd
df = pd.read_csv('GA-2020-05-30.csv')
# df.dtypes
# df.isna().any()
df.info()
Column | Type |
---|---|
pagePath | object |
pageTitle | object |
bounceRate | float64 |
pageViews | int64 |
avgTimeOnPage | float64 |
avgSessionDuration | float64 |
avgPageLoadTime | float64 |
dtype: object |
Bir sonraki adımda da yığına dair istatistiklere bir bakalım.
df.describe()
bounceRate | pageViews | avgTimeOnPage | avgSessionDuration | avgPageLoadTime | |
---|---|---|---|---|---|
count | 5939.000000 | 5939.000000 | 5939.000000 | 5939.000000 | 5939.000000 |
mean | 47.068327 | 67.850985 | 133.914575 | 56.241569 | 2.157740 |
std | 42.735453 | 287.962893 | 225.226011 | 251.591516 | 4.802585 |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
25% | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
50% | 47.500000 | 3.000000 | 17.000000 | 0.000000 | 0.000000 |
75% | 94.682411 | 25.000000 | 194.254808 | 44.715433 | 3.656950 |
max | 105.555556 | 7511.000000 | 2541.000000 | 10531.000000 | 113.786000 |
Sahip olduğumuz ana kütlenin üst, alt ve yığın içerisinden çekilen rastlanısal gözlemlerine bakalım.
df.head(10)
df.tail(10)
df.sample(10)
Bu işlemler neticesinde bounceRate
içerisinde 0 ve 100'e çok yakın değerler olduğunu gördüm. Bu iki değer yakınlığı normal şartlarda pek olası değil. Dolayısıyla, ilgili alan için bir sonraki aşamada süreci biraz daha detaylandırmak gerekecek. Aynı şekilde, pageViews
ve diğer alanlarda da benzer durumların olduğu görülebilmekte. Ek olaak, içeriklerde belirli bir konu akışı takip ediliyorsa değerlerin dağınık olmaması, zamanla belirli bir aralıkta toplanması beklenir.
Sayısal değerler ile ilişkili olarak pagePath
alanına bakıldığında problem olarak nitelendirilebilecek sayısal tutarsızlıkların nedenine dair bazı ipuçları görmek mümkün. Örneğin, /tr/api-nedir/
sayfası gibi bir sayfa seçip sorguladığımda aynı sayfaya ait pek çok ilgili değer olduğu görülebilmekte.
df[df.apply(lambda r: r.str.contains('api-nedir', case=False).any(), axis=1)]
pagePath |
---|
... |
/tr/api-nedir |
/tr/api-nedir/ |
/tr/posts/api-nedir/ |
/tr/posts/api-nedir |
/tr/posts/2017/07/api-nedir |
/tr/posts/2017/07/api-nedir/ |
... |
Ayrıca, path
farklılıklarının yanı sıra, çeşitli sorguların (query) da farklı sayfalar olarak değerlendirilmesi nedeniyle tutarsızlık oluşurduğu görülebilmekte. Bu durumda, ilgili alanlar kaldırılabilir ya da sorguların ve ardından path
sonunda yer alan /
işareti temizlenebilir.
# df[df.apply(lambda p: p.str.endswith('/').any(), axis=1)] # Tüm alanları aramaya dahil etmek için
# df.loc[[p.endswith('/') for p in df.pagePath]] # Belirli bir sütunda işlem yapmak için
df.loc[df.PagePath.str.contains('/api-nedir', case=False, regex=False)]
Kaldırma işlemini tercih etmek çok doğru olmayacaktır. Çünkü, path
ile ilgili değişiklikler zaman içerisinde kasıtlı olarak gerçekleştirilmiş olabilir. Böyle durum mevcut ise, geçerli verileri de silmiş oluruz. Bu nedenle 2. yol üzerinden ilerleyelim.
# df.pagePath = df.pagePath.apply(lambda x: x.split('?')[0].rstrip('/'))
df.pagePath = [p.split('?')[0].rstrip('/') for p in df.pagePath]
Bu aşamadan sonra duplicate veriler netlik kazanmış olacak. Gerekli olmadığı düşünülen satırlar silinebilir ya da toplamlar ve ortalamalar ile tek bir satır haline getirilebilir ya da öncesinde belirli bir değer (örneğin, median
ya da mean
gibi) altındaki veriler de analiz sürecinin dışında tutulabilir. Duplicate kayıtların çoğu bu vesile ile hariç tutulmuş olacaktır.
Matematiksel işlemler yapacak isek, numeric verileri hesaplaması kolay hale getirelim.
#df.bounceRate = [round(p) for p in df.bounceRate]
#df.avgTimeOnPage = [round(p) for p in df.avgTimeOnPage]
#df.avgSessionDuration = [round(p) for p in df.avgSessionDuration]
#df.avgPageLoadTime = [round(p) for p in df.avgPageLoadTime]
df.bounceRate = df.bounceRate.map(round) # percentage
df.avgTimeOnPage = df.avgTimeOnPage.map(round) # seconds
df.avgSessionDuration = df.avgSessionDuration.map(round) # seconds
df.avgPageLoadTime = df.avgPageLoadTime.map(round) # seconds
Bu aşamadan sonrası opsiyonel olarak geçekleştirilebilir. Şayet sadece Google Analytics verileri üzerinde çalışılacaksa elbette başlıklar ve sayfa yolu ile ilgili filtreleme işlemleri yapılabilir. Ancak, Seach Console sayfa verileri ve site haritası üzerinden edinilen güncel veriler de sürece dahil edilecekse (tavsiyem bu şekilde olur) bu durumda gereksiz işlem tekrarlarından kaçınmak adına sayfa başlığı ve filtreleme işlemlerinin tabloların birleştirilmesinden sonra yapılmasını öneririm.
Google Analytics bir sayfa 404 durum koduna sahip olsa dahi path
ve başlık değeri iletir. Eğer bir 404 sayfası var ise bu sayfa tarafından üretilen başlık değeri gönderilir. Bu durumda, silinmiş ve/veya taşınmış sayfaların başlıklarını Page Not Found gibi değerler üzerinden ayırt edebilir ve bu kayıtları indeks değerlerini kullanarak veri tablosundan kaldırabiliriz.
df.drop(df.index[df.pageTitle.str.contains('found|error|not set', case=False, regex=True)], axis=0, inplace=True)
Elbette bu listeye sabit, form, varış ve listeleme sayfalarını da dahil edebiliriz. Tekrar, değerlerdeki olası değişime bir göz atalım.
df.describe()
Verilerin Yorumlanması
Yığındaki gözlem sayısında epey bir azalma olmuş olmalı. Artık aşama aşama belirli durumlara odaklanabiliriz. İlk olarak histogram grafiği ile dağılımları inceleyelim.
df.hist(figsize=(20, 10), grid = True, bins=25, color='#007bff', zorder=2, rwidth=0.9);

Ardından, verinin merkez etrafındaki yayılımını görmek adına boxplot grafiğini oluşturalım.
color = {
"boxes": "DarkGreen",
"whiskers": "DarkOrange",
"medians": "DarkBlue",
"caps": "Gray",
}
df.plot.box(vert=False, figsize=(20, 10), showmeans=True, color=color);

Görüldüğü üzere, neredeyse tüm alanlarda aykırı değerler mevcut. Elbette her biri için grafiği detayladırıp verilere daha yakından bakabilir ve daha net fikir edinebiliriz.
df.avgPageLoadTime.plot.box(vert=False, figsize=(20,10), showmeans=True, color=color);
Önemli olan gösterim almış ancak yüklenme süresi ve hemen çıkma oranı yüksek olan sayfaları mümkün olduğu kadar ortalamaya yakın hale getirebilmek. Bunun için de ilk hedef aykırı değerleri en azından 1.5*(Q3-Q1)
aralığına dahil etmek.
df[
(df.bounceRate <= df.bounceRate.median()) &
(df.avgTimeOnPage <= df.avgTimeOnPage.median()) &
(df.avgSessionDuration <= df.avgSessionDuration.median()) &
(df.avgPageLoadTime <= df.avgPageLoadTime.median())
]
pagePath | pageTitle | bounceRate | pageViews | avgTimeOnPage | avgSessionDuration | avgPageLoadTime | |
---|---|---|---|---|---|---|---|
925 | /tr/grav-markdown-icerik-yazimi | Grav Makdown İçerik Yazımı | 50 | 66 | 109 | 26 | 0 |
1260 | /tr/html5-api-kullanimlari-1 | HTML5 ve API Kullanımları | 48 | 35 | 130 | 17 | 0 |
1263 | /tr/mermaid-js | Mermaid JS Nedir? | 33 | 35 | 109 | 19 | 0 |
1392 | /tr/parse-urls-url-yapisi-cozumlemesi | URL Yapısı Çözümlemesi | 31 | 29 | 0 | 20 | 0 |
1401 | /tr/wodpress-grav-cms-gecisi | WordPress - GRAV CMS Geçişi | 0 | 29 | 37 | 0 | 0 |
... | ... | ... | ... | ... | ... | ... | ... |
5879 | /tr/wordpress-posts-markdown | WordPress İçeriklerin Markdown Olarak Dönüştür... | 0 | 1 | 3 | 0 | 0 |
5886 | /tr/wp-ads-conversion-tracking | WordPress Eklentisi: 'WP-Ads Conversion Tracki... | 0 | 1 | 51 | 0 | 1 |
5888 | /tr/wp-cli-ile-post-regex-img-search | WP-Cli İle Post İçeriklerinde Arama İşlemi | 0 | 1 | 11 | 0 | 0 |
5891 | /tr/wp-cli-post-meta-thumbnail | WP-CLI Post ve Meta İle Tüm Postlara Aynı Görs... | 0 | 1 | 1 | 0 | 0 |
5894 | /tr/wp-cron-nedir-nasil-kullanilir | WP-Cron Nedir? Nasıl Kullanılır? | 0 | 1 | 6 | 0 | 0 |
345 rows × 7 columns |
Elimizde tüm durumlarda median
altında kalan içeriklerin listesi var. Bu içerikler ile alakalı olarak şu soruları sorabiliriz;
- Bu içerikler ne sıklıkla güncelleniyor?
- İçerikler arama motorları tarafından erişilebiliyor mu?
- İçeriklerin görüntülenmesini engelleyen teknik bir problem mi var?
- İçerik ilgi çekici değil mi? İçeriğe dair ilgili arama hacmi nedir?
- İçerik yeterli derecede açıklayıcı değil mi?
- İçeriğin sıralama aralığı nedir? Rekabette çok mu geride?
İçeriklerden bir kaçına bakıldığında arama hacimlerinin oldukça düşük olduğu ve içeriğin zayıf olduğu görülebilmekte. O halde başlangıç noktamız artık belirgin. Önceliğimiz bu içerikleri zenginleştirmek ve odaklanılan konuyu olası yakın alakalı sorgularla eşleşecek şekilde zenginleştirmek. Sadece bu içerikleri ayrı bir tablo olarak kayı altına alıp zamana bağlı değişimi takip edilebilir.
Sayfalarla ilgili işlemleri bölümlendirirken quantile
dilimleri üzerinden de ilerleyebiliriz. Neticede amacımız düşük gösterime sahip, hemen çıkma oranı yüksek ve bir nedenle ziyaretçilerin vakit harcamak istemedikleri içerikleri ilgi çekici kılmak ya da bu içerikleri kaldırmak.
df.loc[df.avgPageLoadTime > df.avgPageLoadTime.quantile(.75)].sort_values(by=['avgPageLoadTime', 'bounceRate'], ascending=[False, False], axis=0)
Evet, bir sonraki aşamada ortalama sayfa yükleme süresi bağlamında sayfaların performansına ve performans değerlerine bağlı olarak neler yapılabileceğine bakılabilir.

Gerekli ise teknik geliştirmeler yapldıktan sonra yakın bir zaman aralığı için değişimler takip edilebilir ve yukarıdaki işlemler -birkaçı zaman içerisinde gereksiz hale gelecektir- döngüsel olarak tekrarlanabilir.

Kelime tekrarlarını değerlendirmek için genel ya da sadece belirli bir metrik (örneğin; pageView) bağlamında da kelime bulutu oluşturabilirsiniz. Elbette bu tür durumlarda, web sitesinde son ek (suffix) kullanılıyor ise ilgili eklerin önceden temizlenmesi gerekir.
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
text = ' '.join(k for k in list(df.pageTitle))
stopwords = set(STOPWORDS)
stopwords.update(['ve','ile','user','behavior'])
wordcloud = WordCloud(max_font_size=50, max_words=100, background_color="white").generate(text)
plt.figure(figsize=(20,14), dpi=200)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.tight_layout(pad=0)
plt.savefig('myfigure_200.png')
plt.show()
Evet, işlemlerimiz bu kadar. Unutmamak gerekir ki genel değerlendirme içerisinde metriklerin ortalamaları esas kılınacak iken belirli bir metrik önceliğinde elde edilecek sonuç daha doğru bir yorum yapmayı mümkün kılabilir. Bu nedenle, metrikler temelinde neden-sonuç ilişkisi çerçevesinde önem göserilmelidir.