Veri işleme uygulaması

Şimdiye kadar koleksiyonları, arama yöntemlerini, sıralamayı ve maliyet sezgisini ayrı ayrı inceledik. Bu hafta bunları tek bir veri işleme akışında birleştireceğiz.

Örnek bağlamımız bir CSV dosyasından gelen ürün kayıtları olacak. Web sürümünde tarayıcı içinde çalışabilmek için CSV metnini StringIO ile dosya benzeri bir kaynak olarak kullanacağız. Aynı kod gerçek bir dosyada open() ile de uygulanabilir.

1 Bu hafta neleri yapabilmelisiniz?

Bölümün sonunda:

  • CSV/metin verisini kayıtlara dönüştürebilmeli,
  • sayısal alanlarda tür dönüşümü yapabilmeli,
  • veriyi filtreleyebilmeli, gruplayabilmeli ve sıralayabilmeli,
  • kayıtları anahtara göre indeksleyebilmeli,
  • arama ve özetleme işlemlerini fonksiyonlara ayırabilmeli,
  • hatalı veya eksik veri durumlarını fark edebilmeli,
  • bir veri işleme görevi için uygun koleksiyonları 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

Her satır bir kaydı, ilk satır ise alan adlarını gösterir.

3 CSV metnini okumak

#| edit: false
#| completion: false
import csv
from io import StringIO

csv_text = """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
"""

reader = csv.DictReader(StringIO(csv_text))
records = list(reader)

print(records[0])
print(records[0]["price"], type(records[0]["price"]).__name__)

csv.DictReader, her satırı sözlük olarak üretir. Ancak CSV’den gelen alanlar başlangıçta string’dir. "12.5" sayısal karşılaştırma için önce float yapılmalıdır.

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

#| edit: false
#| completion: false
import csv
from io import StringIO

csv_text = """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
"""

records = []
for row in csv.DictReader(StringIO(csv_text)):
    row["price"] = float(row["price"])
    row["stock"] = int(row["stock"])
    records.append(row)

print(records[0])
print(type(records[0]["price"]).__name__)
print(type(records[0]["stock"]).__name__)

Tür dönüşümünü erken yapmak, sonraki adımları sadeleştirir.

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)

Web kitabındaki örneklerde dosya dağıtımıyla uğraşmamak için StringIO kullanıyoruz; veri işleme mantığı aynıdır.

6 İşleme hattı

Bir veri işleme görevini şu aşamalarla düşünmek yararlıdır:

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 girdisi ve çıktısı belirgin olduğunda hata ayıklamak kolaylaşır.

7 Filtreleme

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

#| edit: false
#| completion: false
records = [
    {"code": "K101", "name": "Kalem", "category": "Kırtasiye", "price": 12.5, "stock": 40},
    {"code": "D205", "name": "Defter", "category": "Kırtasiye", "price": 45.0, "stock": 8},
    {"code": "M310", "name": "Mouse", "category": "Elektronik", "price": 325.0, "stock": 12},
    {"code": "K420", "name": "Klavye", "category": "Elektronik", "price": 590.0, "stock": 5},
]

low_stock = []
for record in records:
    if record["stock"] < 10:
        low_stock.append(record)

for record in low_stock:
    print(record["name"], record["stock"])

8 Gruplama

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

#| edit: false
#| completion: false
records = [
    {"name": "Kalem", "category": "Kırtasiye", "price": 12.5},
    {"name": "Defter", "category": "Kırtasiye", "price": 45.0},
    {"name": "Mouse", "category": "Elektronik", "price": 325.0},
    {"name": "Klavye", "category": "Elektronik", "price": 590.0},
]

groups = {}

for record in records:
    category = record["category"]
    if category not in groups:
        groups[category] = []
    groups[category].append(record)

for category, products in groups.items():
    print(category, "->", len(products), "ürün")

Bu, 3. ve 4. haftalarda gördüğümüz sözlük + liste kalıbıdır.

9 Kategori toplamı

Bu kez kategori başına ürün değerini toplayalım:

#| edit: false
#| completion: false
records = [
    {"name": "Kalem", "category": "Kırtasiye", "price": 12.5, "stock": 40},
    {"name": "Defter", "category": "Kırtasiye", "price": 45.0, "stock": 8},
    {"name": "Mouse", "category": "Elektronik", "price": 325.0, "stock": 12},
    {"name": "Klavye", "category": "Elektronik", "price": 590.0, "stock": 5},
]

category_values = {}

for record in records:
    category = record["category"]
    value = record["price"] * record["stock"]
    category_values[category] = category_values.get(category, 0) + value

for category, value in category_values.items():
    print(category, round(value, 2))

Burada dict değerlerini liste değil, toplam tutan sayılar olarak kullandık.

10 Koda göre indeks oluşturmak

Aynı veri üzerinde ürün koduna göre sık sık arama yapacaksak kayıtları her defasında doğrusal aramak yerine bir indeks oluşturabiliriz:

#| edit: false
#| completion: false
records = [
    {"code": "K101", "name": "Kalem", "stock": 40},
    {"code": "D205", "name": "Defter", "stock": 8},
    {"code": "M310", "name": "Mouse", "stock": 12},
]

by_code = {}
for record in records:
    by_code[record["code"]] = record

print(by_code["M310"])

Bu noktada verinin iki görünümü vardır:

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

Aynı kayıt sözlüklerinin iki yapı tarafından referans edildiğine dikkat edin.

11 Sıralama

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

#| edit: false
#| completion: false
records = [
    {"name": "Kalem", "category": "Kırtasiye", "price": 12.5},
    {"name": "Defter", "category": "Kırtasiye", "price": 45.0},
    {"name": "Mouse", "category": "Elektronik", "price": 325.0},
    {"name": "Klavye", "category": "Elektronik", "price": 590.0},
]

ordered = sorted(
    records,
    key=lambda record: (record["category"], record["price"]),
)

for record in ordered:
    print(record["category"], record["price"], record["name"])

12 Fonksiyonlara ayırma

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

#| edit: false
#| completion: false
import csv
from io import StringIO


def load_products(csv_text):
    products = []

    for row in csv.DictReader(StringIO(csv_text)):
        row["price"] = float(row["price"])
        row["stock"] = int(row["stock"])
        products.append(row)

    return products


def low_stock_products(products, limit=10):
    result = []
    for product in products:
        if product["stock"] < limit:
            result.append(product)
    return result


def index_by_code(products):
    result = {}
    for product in products:
        result[product["code"]] = product
    return result


csv_text = """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
"""

products = load_products(csv_text)
print(low_stock_products(products))
print(index_by_code(products)["K101"])

Her fonksiyonun tek bir göreve odaklanması test etmeyi kolaylaştırır.

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

Aşağıdaki fonksiyon her kategori için toplam stok miktarını döndüren bir sözlük üretmelidir.

#| exercise: hafta12-category-stock
#| completion: false
#| persist: true
def stock_by_category(products):
    totals = {}

    for product in products:
        # Burayı tamamlayın.
        pass

    return totals

products = [
    {"name": "Kalem", "category": "Kırtasiye", "stock": 40},
    {"name": "Defter", "category": "Kırtasiye", "stock": 8},
    {"name": "Mouse", "category": "Elektronik", "stock": 12},
    {"name": "Klavye", "category": "Elektronik", "stock": 5},
]

print(stock_by_category(products))

Beklenen sonuç:

{'Kırtasiye': 48, 'Elektronik': 17}
#| exercise: hafta12-category-stock
#| check: true
scope = {}
try:
    exec(user_code, scope)
    fn = scope.get("stock_by_category")
    sample = [
        {"name": "A", "category": "X", "stock": 2},
        {"name": "B", "category": "Y", "stock": 3},
        {"name": "C", "category": "X", "stock": 5},
    ]
    correct = callable(fn) and fn(sample) == {"X": 7, "Y": 3} and fn([]) == {}
except Exception:
    correct = False

feedback = (
    {"correct": True, "message": "Kategori stok toplamları doğru hesaplandı."}
    if correct
    else {"correct": False, "message": "category değerini anahtar olarak kullanın ve totals.get(category, 0) üzerine stock ekleyin."}
)
feedback

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

Solution.

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("") hata üretir. Hatanın nerede yönetileceği uygulamanın gereksinimine bağlıdır. Basit bir yaklaşım hatalı satırı raporlayıp atlamak olabilir:

#| edit: false
#| completion: false
import csv
from io import StringIO

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

valid = []
errors = []

for line_no, row in enumerate(csv.DictReader(StringIO(csv_text)), start=2):
    try:
        row["price"] = float(row["price"])
        row["stock"] = int(row["stock"])
        valid.append(row)
    except ValueError:
        errors.append(line_no)

print("Geçerli:", valid)
print("Hatalı satırlar:", errors)

Burada hatayı sessizce yok etmek yerine hangi satırların sorunlu olduğunu kaydediyoruz.

15 Uçtan uca küçük rapor

Bir veri işleme görevinde artık şu zinciri kurabiliyoruz:

  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.

Bu adımların her biri daha önce öğrendiğiniz bir veri yapısı veya algoritma kararına dayanır.

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 verisi csv.DictReader ile sözlük kayıtlarına dönüştürülebilir.
  • Sayısal alanların string olarak gelmesi nedeniyle tür dönüşümü gerekir.
  • Filtreleme, gruplama, indeksleme, sıralama ve özetleme aynı veri akışında birlikte kullanılabilir.
  • Fonksiyonlara ayırma veri işleme kodunu test edilebilir ve okunabilir kılar.
  • Hatalı veri durumları açıkça ele alınmalıdır.
  • 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 çok sık erişim için neden ayrı bir sözlük indeksi yararlı olabilir?
  3. Filtreleme ile gruplama arasındaki fark nedir?
  4. Hatalı CSV satırını sessizce atmak neden sorun olabilir?
  5. Aynı kaydı hem liste hem sözlük indeksinden görmek referans açısından ne anlama gelir?
Back to top