Veri analizi denince akla genelde grafikler ve modeller geliyor. Ama gerçek bir projede zamanın büyük kısmı ondan önceki aşamaya gidiyor: veriyi bulmak, indirmek, tabloya çevirmek ve temizlemek. Bu iki bölümlük hafta sonu projesinde Süper Lig’in 2024-25 sezonunu inceleyeceğiz. Bu ilk bölüm tamamen o görünmeyen ama en önemli işe ayrıldı.
Sonunda elimizde her satırı bir maç olan, kontrol edilmiş, temiz bir CSV dosyası olacak. Bir de veriye ilk bakış: Ev sahibi olmak gerçekten avantaj mı?
Bu projeyi seçmemin küçük bir hikâyesi var. Ben Beşiktaşlıyım ve yeni stadımız yapılırken bir dönem kendi stadımız olmadan oynadık. İç saha maçlarını kendi evinde oynayamamak bir takım için ne demek? Ev sahibi olmak gerçekten bir avantaj mı, yoksa sadece taraftarların inandığı bir şey mi? Bu soruların cevabını veride aramak istedim. Bu ilk bölüm o yolculuğun hazırlık kısmı.
Kodların hepsi Colab’da hazır: Colab’da Aç
Veri nereden geliyor?
Veriyi openfootball projesinden alıyoruz. Bu proje, dünyanın birçok liginin maç sonuçlarını gönüllülerin derlediği açık dosyalarda topluyor ve GitHub’da yayımlıyor. Lisansı CC0, yani verinin üzerinde telif hakkı yok; istediğin gibi kullanabilirsin.
Bir uyarı: Bu resmî bir kaynak değil. Gönüllüler tarafından derlendiği için eksik ya da hatalı kayıtlar olabilir. Zaten bu bölümün amacı da tam olarak bunları bulmak.
Veri setini seçerken her zaman lisansına bak. “İnternette buldum” demek, onu bir projede ya da yazıda kullanabileceğin anlamına gelmiyor.
1. Veriyi indirmek
Veri tek bir JSON dosyasında. Python’un requests kütüphanesiyle indiriyoruz:
import requestsURL = "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/tr.1.json"cevap = requests.get(URL, timeout=30)cevap.raise_for_status()ham = cevap.json()print(ham["name"]) # Turkish Süper Lig 2024/25print(len(ham["matches"]), "maç") # 342 maç
raise_for_status() bir güvenlik kemeri: Sunucu bir hata döndürürse (sayfa bulunamadı gibi) program burada durup hatayı söylüyor. Python Dersi 13’teki raise‘in hazır bir örneği.
Tek bir maç kaydı şöyle görünüyor:
{ "round": "Matchday 1", "date": "2024-08-09", "time": "21:00", "team1": "Galatasaray", "team2": "Hatayspor", "score": {"ht": [0, 0], "ft": [2, 1]}}
team1 ev sahibi, team2 deplasman. ft (full time) maç sonu, ht (half time) ilk yarı skoru. Bu yapı, Python Dersi 8’de öğrendiğimiz sözlük ve listelerin iç içe geçmiş hali.
2. JSON’dan tabloya
Pandas’ın json_normalize fonksiyonu iç içe sözlükleri düzleştirip tabloya çeviriyor:
import pandas as pdimport numpy as npdf = pd.json_normalize(ham["matches"])print(df.shape) # (342, 8)
score sözlüğünün içindeki iki alan score.ft ve score.ht adında iki ayrı sütuna dönüştü. Ama her hücrede halâ [2, 1] gibi bir liste var; bunu da ayırmamız gerekecek.
3. Sütunları düzenlemek
df = df.rename(columns={ "round": "hafta", "team1": "ev", "team2": "deplasman", "score.ft": "skor", "score.ht": "ilk_yari", "status": "durum",})df["hafta"] = df["hafta"].str.extract(r"(\d+)").astype(int)df["tarih"] = pd.to_datetime(df["date"] + " " + df["time"])def ayir(skor, i): """[ev, deplasman] listesinden i. elemanı alır; skor yoksa NaN döndürür.""" return skor[i] if isinstance(skor, list) else np.nandf["ev_gol"] = df["skor"].apply(ayir, i=0)df["dep_gol"] = df["skor"].apply(ayir, i=1)df["iy_ev"] = df["ilk_yari"].apply(ayir, i=0)df["iy_dep"] = df["ilk_yari"].apply(ayir, i=1)df["hukmen"] = df["durum"].eq("awarded")
Bu blokta üç önemli temizlik adımı var:
- “Matchday 1” metninden 1 sayısına:
str.extract(r"(\d+)")metnin içindeki rakamları çekiyor. Metin olarak kalırsa “Matchday 10” sıralamada “Matchday 2″den önce gelirdi. - Tarih ve saati tek bir zaman sütununa:
to_datetimeile gerçek bir tarih tipine çevirince haftanın gününü, ayı ya da iki maç arasındaki gün sayısını kolayca hesaplayabiliyoruz. - Skor listelerini ayırmak:
ayirfonksiyonu listeden golü alıyor. Skor yoksa hata vermek yerineNaN(boş değer) döndürüyor. Bu yüzdenisinstanceile önce gerçekten bir liste olup olmadığına bakıyoruz.
4. Eksik değerler: verinin anlatmadığı hikaye
df.isna().sum()
ev_gol 4dep_gol 4iy_ev 30iy_dep 30
Veride üç farklı sorun çıktı:
- Skoru olmayan 4 maç: Hepsi 38. haftada. Kaynak veride bu maçların skoru girilmemiş. Bu maçları analizden çıkaracağız, ama bunu yazıda açıkça söylemek şart: Sonuçlarımız 342 değil, 338 maça dayanacak.
- İlk yarı skoru olmayan 30 maç: Bu 30 maçın 4’ü yukarıdaki skoru hiç olmayan maçlar. Geri kalan 26 maçta maç sonucu var ama ilk yarı skoru yok. Maç sonucuyla ilgili analizlerde bu bir sorun değil; ilk yarıyla ilgili bir soru sorarsak bu maçları dışarıda bırakmamız gerekecek.
- Hükmen sonuçlanan 1 maç: 23. haftadaki Galatasaray-Adana Demirspor maçı veride
awarded(hükmen) olarak işaretli, skoru 3-0. Bu skor sahada atılan gollerden gelmiyor. Puan tablosunu hesaplarken bu maç sayılmalı, ama “maç başına gol” gibi sahayla ilgili bir istatistiğe katılmamalı.
Eksik değerlerde verilecek kararların (silmek, doldurmak, ayrı tutmak) tek bir doğru cevabı yok. Doğru cevap sorduğun soruya bağlı. Ama her durumda kural aynı: Ne yaptığını ve neden yaptığını yaz.
5. Tutarlılık kontrolleri
Eksik değerler sadece bir tür sorun. Veri “tam” görünüp yanlış da olabilir. Bunu yakalamak için veri hakkında bildiğimiz gerçeklerle karşılaştırıyoruz:
print("Haftadaki maç sayıları:", df.groupby("hafta").size().unique())print("Takım sayısı:", pd.concat([df["ev"], df["deplasman"]]).nunique())print("Takım başına maç:", pd.concat([df["ev"], df["deplasman"]]).value_counts().unique())print("Aynı ev-deplasman eşleşmesi en fazla:", df.groupby(["ev", "deplasman"]).size().max(), "kez")
Haftadaki maç sayıları: [9]Takım sayısı: 19Takım başına maç: [36]Aynı ev-deplasman eşleşmesi en fazla: 1 kez
Hepsi beklendiği gibi: 19 takımlı bir ligde her hafta bir takım bay geçiyor, kalan 18 takım 9 maç yapıyor. Her takım diğer 18 takımla bir iç sahada, bir deplasmanda oynadığı için 36 maç yapıyor. Aynı eşleşme iki kez yazılmamış. Ayrıca takım adları tutarlı: “Fenerbahçe” ile “Fenerbahce” gibi aynı takımın iki farklı yazımı olsaydı, takım sayısı 19’dan fazla çıkardı.
Bu kontroller sıkıcı görünebilir ama veri analizinin en değerli dakikaları bunlar. Hatalı bir veriyle yapılan en güzel grafik bile yanlış bir şey söyler.
6. Yeni sütunlar ve temiz dosya
Skoru olan 338 maçı alıp iki yeni sütun ekliyoruz: toplam gol ve maçın sonucu.
oynanan = df.dropna(subset=["ev_gol", "dep_gol"]).copy()oynanan[["ev_gol", "dep_gol"]] = oynanan[["ev_gol", "dep_gol"]].astype(int)oynanan["toplam_gol"] = oynanan["ev_gol"] + oynanan["dep_gol"]oynanan["sonuc"] = np.select( [oynanan["ev_gol"] > oynanan["dep_gol"], oynanan["ev_gol"] < oynanan["dep_gol"]], ["Ev sahibi", "Deplasman"], default="Beraberlik",)oynanan.to_csv("super_lig_2024_25_temiz.csv", index=False)
np.select bir dizi koşulu sırayla kontrol ediyor: Ev sahibinin golü fazlaysa “Ev sahibi”, deplasmanınki fazlaysa “Deplasman”, hiçbiri değilse “Beraberlik”. Python Dersi 5’teki if-elif-else‘in bütün sütuna tek seferde uygulanan hali.
Neden temiz veriyi ayrı bir dosyaya kaydediyoruz? Bir sonraki bölümde analize doğrudan bu dosyadan başlayacağız. Temizlik adımlarını her seferinde tekrarlamak hem zaman kaybı hem de hata riski.
7. İlk bakış: ev sahibi avantajı var mı?
Temiz veriyle ilk soruyu soralım. Hükmen maçı bu kez dışarıda bırakıyoruz, çünkü sahada oynananlara bakıyoruz:
sahada = oynanan[~oynanan["hukmen"]]print(f"Maç başına gol: {sahada['toplam_gol'].mean():.2f}")print(sahada["sonuc"].value_counts(normalize=True).round(3))
Sahada oynanan maç: 337Maç başına gol: 2.93Ev sahibi gol ortalaması: 1.66Deplasman gol ortalaması: 1.27Ev sahibi 0.484Deplasman 0.282Beraberlik 0.234

Verideki maçların neredeyse yarısını ev sahibi kazanmış, deplasman ekibi ise dört maçtan sadece birinden biraz fazlasını. Ev sahipleri maç başına deplasman takımlarından yaklaşık 0,4 gol fazla atmış. Bu tek sezonluk bir gözlem; ev sahibi avantajının nedenleri ve takımlar arasındaki farklar, ikinci bölümün konusu.
Sezonun en golü maçı: 36. haftada Çaykur Rizespor 6-3 Göztepe.
Bu bölümde ne yaptık?
- Açık lisanslı bir veri kaynağı bulduk ve lisansını kontrol ettik.
- JSON’u indirip
json_normalizeile tabloya çevirdik. - Hafta numarasını, tarihi ve skorları kullanılabilir sütunlara dönüştürdük.
- Eksik skorları, eksik ilk yarı skorlarını ve hükmen maçı bulup her biri için bir karar verdik.
- Veriyi bildiğimiz gerçeklerle karşılaştırarak tutarlılığını kontrol ettik.
- Temiz veriyi kaydedip ilk soruyu sorduk.
Kendin dene
- URL’deki
tr.1yerineen.1yazarak aynı sezonun Premier Lig verisini indir ve aynı temizliği uygula. Hangi kontroller farklı sonuç veriyor? - İlk yarı skoru olan maçlarda, ilk yarıyı önde kapatan takım maçı kaç kez kazanmış?
- Haftalara göre maç başına gol ortalamasını hesapla. Sezon ilerledikçe değişiyor mu?
- Ev sahibi kazanma oranı en yüksek olan takım hangisi?
Sıradaki bölüm
Futbol Veri Projesi 2’de temiz veriden puan tablosunu hesaplıyoruz, takımların iç saha ve deplasman performansını karşılaştırıyoruz. Bir de önemli bir uyarıyı konuşacağız: Maç sonuçlarından hesaplanan bir puan tablosu, eksik maçlar ve puan silme cezaları gibi nedenlerle resmî tablodan farklı olabilir.

Bir yanıt yazın