Veri işleme uygulaması

Şimdiye kadar koleksiyonları, arama yöntemlerini, sıralamayı ve işlem maliyetini ayrı ayrı gördük. Bu hafta hepsini tek bir iş üzerinde kullanacağız: bir mağazanın ürün listesini okuyup düşük stoklu ürünleri, kategori toplamlarını ve sıralı bir rapor çıkaracağız.

Ürün kayıtları bir CSV dosyasından (her satırda değerlerin virgülle ayrıldığı metin dosyası) gelecek. Örnekler tarayıcıda çalışsın diye CSV metnini StringIO ile dosyaymış gibi kullanacağız. Aynı kod, open() ile açılan gerçek bir dosyada da çalışır.

1 Bu hafta neleri yapabilmelisiniz?

Bölümün sonunda:

  • CSV metnini kayıtlara dönüştürebilmeli,
  • Sayısal alanlarda tür dönüşümü yapabilmeli,
  • Veriyi filtreleyebilmeli, gruplayabilmeli ve sıralayabilmeli,
  • Kayıtlardan bir anahtara göre indeks oluşturabilmeli,
  • Arama ve özetleme işlemlerini fonksiyonlara ayırabilmeli,
  • Hatalı ya da eksik veriyi fark edebilmeli,
  • Bir veri işleme görevinde hangi koleksiyonu neden seçtiğinizi gerekçelendirebilmelisiniz.

2 Örnek veri

Elimizde şu CSV olduğunu düşünelim:

code,name,category,price,stock
K101,Kalem,Kırtasiye,12.5,40
D205,Defter,Kırtasiye,45.0,8
M310,Mouse,Elektronik,325.0,12
K420,Klavye,Elektronik,590.0,5
S115,Silgi,Kırtasiye,8.0,55

İlk satırda alan adları var. Sonraki her satır bir ürünün kaydıdır.

3 CSV metnini okumak

csv.DictReader her satırı bir sözlük olarak verir. Sözlüğün anahtarları ilk satırdaki alan adlarıdır. Ama CSV’den gelen bütün değerler string’dir. "12.5" değerini sayı gibi karşılaştırmak için önce float’a dönüştürmeniz gerekir.

4 Türleri dönüştürmek

Türleri okur okumaz dönüştürürseniz sonraki adımlarda her seferinde dönüştürmekle uğraşmazsınız.

5 Gerçek dosyada aynı fikir

Gerçek bir products.csv dosyası için temel kalıp şöyledir:

import csv

with open("products.csv", encoding="utf-8", newline="") as file:
    reader = csv.DictReader(file)
    for row in reader:
        print(row)

Size ayrıca dosya indirtmemek için web kitabındaki örneklerde StringIO kullanıyoruz. Veriyi işleyen kod aynı kalır.

6 İşleme hattı

Bir veri işleme görevini aşamalara ayırarak düşünün:

flowchart LR
    A["Ham CSV"] --> B["Oku"]
    B --> C["Türleri dönüştür"]
    C --> D["Doğrula"]
    D --> E["Filtrele / grupla"]
    E --> F["Ara / sırala"]
    F --> G["Özet / rapor"]

Her aşamanın neyi alıp neyi verdiği belli olursa bir hatanın hangi aşamada çıktığını bulmak kolaylaşır.

7 Filtreleme

Stoku 10’un altında olan ürünleri bulalım:

8 Gruplama

Ürünleri kategoriye göre gruplayalım:

Bu, 3. ve 4. haftalarda gördüğümüz kalıp: her anahtarın değeri bir liste olan sözlük.

9 Kategori toplamı

Bu kez her kategorideki ürünlerin toplam değerini (fiyat × stok) hesaplayalım:

Bu kez sözlüğün değerleri liste değil, toplamı tutan sayılar.

10 Koda göre indeks oluşturmak

Aynı veride ürün koduna göre sık sık arama yapacaksak listeyi her seferinde baştan sona taramak zorunda değiliz. Kodu anahtar, kaydı değer yapan bir sözlük kurarız. Buna indeks denir:

Artık aynı veriye iki yoldan ulaşabiliriz:

  • records: sırayı koruyan kayıt listesi,
  • by_code: koda göre hızlı erişim için sözlük.

Dikkat: iki yapı da aynı kayıt sözlüklerini gösterir, kayıtlar kopyalanmaz. Bir kaydı birinden değiştirirseniz değişikliği ötekinde de görürsünüz.

11 Sıralama

Ürünleri önce kategoriye, sonra aynı kategori içinde fiyata göre sıralayalım:

12 Fonksiyonlara ayırma

Veri işleme kodunun tamamını tek blokta tutmak yerine adımları fonksiyonlara ayırabiliriz:

Her fonksiyon tek bir iş yaptığı için her birini ayrı ayrı test edebilirsiniz.

13 Alıştırma: kategoriye göre özet

Aşağıdaki fonksiyonu tamamlayın. Fonksiyon, her kategorinin toplam stok miktarını tutan bir sözlük döndürmeli.

Beklenen sonuç:

{'Kırtasiye': 48, 'Elektronik': 17}

totals[category] = totals.get(category, 0) + product["stock"] kalıbını deneyin.

def stock_by_category(products):
    totals = {}

    for product in products:
        category = product["category"]
        totals[category] = totals.get(category, 0) + product["stock"]

    return totals

14 Hatalı veriyle ne yapacağız?

Gerçek CSV her zaman temiz olmayabilir:

code,name,category,price,stock
K101,Kalem,Kırtasiye,12.5,40
D205,Defter,Kırtasiye,abc,8
M310,Mouse,Elektronik,325.0,

float("abc") ve int("") ValueError verir. Bu hatayla nerede ve nasıl başa çıkılacağı, programdan ne beklendiğine bağlıdır. Basit bir yol, hatalı satırı kaydedip atlamaktır:

Hatayı sessizce geçiştirmiyoruz, hangi satırların sorunlu olduğunu kaydediyoruz. Satır numarası 2’den başlar, çünkü 1. satırda alan adları var.

15 Uçtan uca küçük rapor

Artık bir veri işleme görevinde şu adımları arka arkaya yapabiliyoruz:

  1. CSV’yi oku,
  2. Türleri dönüştür,
  3. Hatalı satırları ayır,
  4. Düşük stokları filtrele,
  5. Kategori toplamlarını hesapla,
  6. Ürünleri ölçüte göre sırala,
  7. Koda göre indeks oluştur.

Her adımda daha önce öğrendiğiniz bir veri yapısını ya da algoritmayı kullanıyorsunuz.

16 Hangi yapıyı neden kullandık?

İhtiyaç Yapı
kayıtların sırasını korumak list
bir kaydın alanları dict
ürün kodundan kayda erişmek dict
kategori başına toplam dict
benzersiz kategori adları gerekirse set
sıralı rapor sorted() sonucu list

17 Bölüm özeti

  • csv.DictReader CSV’nin her satırını bir sözlük olarak verir.
  • Sayısal alanlar string olarak geldiği için tür dönüşümü gerekir.
  • Filtreleme, gruplama, indeks oluşturma, sıralama ve özetleme aynı programda arka arkaya kullanılabilir.
  • Veri işleme kodunu fonksiyonlara ayırmak, kodu test etmeyi ve okumayı kolaylaştırır.
  • Hatalı veriyi sessizce atlamayın, hangi satırın sorunlu olduğunu kaydedin.
  • Koleksiyon seçimi, verinin ne olduğundan çok veri üzerinde hangi işlemlerin yapılacağına bağlıdır.

18 Kendinizi kontrol edin

  1. DictReader ile okunan price alanı neden hemen sayı olmayabilir?
  2. Ürün koduna göre sık sık erişilecekse ayrı bir sözlük indeksi neden işe yarar?
  3. Filtreleme ile gruplama arasındaki fark nedir?
  4. Hatalı CSV satırını sessizce atmak neden sorun olabilir?
  5. Aynı kayda hem listeden hem sözlük indeksinden ulaşabilmek, referans açısından ne anlama gelir?
Back to top