Pandas Apply, Map i Lambda: Vodič za primjenu funkcija na podatke

Naučite kako koristiti pandas apply(), map() i lambda funkcije za prilagođene transformacije podataka. Vodič pokriva razlike između metoda, praktične primjere, performanse i pipe() ulančavanje — ažurirano za pandas 3.x.

Ako ste pratili našu seriju o pandas biblioteci — od čišćenja podataka, preko GroupBy operacija, spajanja DataFrame-ova, do preoblikovanja s pivot_table i melt — onda već imate solidne temelje za rad s podacima. No što kad vam ugrađene pandas metode jednostavno nisu dovoljne? Što napraviti kada trebate primijeniti vlastitu logiku, neki prilagođeni izračun ili složenu transformaciju na svaki red, stupac ili element?

E tu na scenu stupaju tri ključna alata: apply(), map() i lambda funkcije. Iskreno, ovo su metode koje ćete koristiti gotovo svakodnevno — bilo da kategorizirate podatke prema prilagođenim pravilima, transformirate tekstualne vrijednosti ili gradite potpuno nove značajke iz postojećih stupaca. U ovom vodiču pokrit ćemo sve što trebate znati, uključujući novosti iz pandas 3.x verzije.

Što su apply(), map() i lambda u pandas-u?

Prije nego zaronimo u primjere, razjasnimo osnove. Svaka od ovih metoda ima drugačiju namjenu i opseg djelovanja:

  • apply() — najfleksibilnija metoda. Radi na Series-u i DataFrame-u. Može primjenjivati funkcije po redovima, stupcima ili elementima.
  • map() — radi element po element. U pandas 3.x funkcionira i na Series-u i na DataFrame-u (zamijenila je zastarjelu metodu applymap()).
  • Lambda funkcije — anonimne jednoline funkcije koje se najčešće koriste unutar apply() ili map() za brze transformacije bez potrebe za zasebnom definicijom.

Zvuči jednostavno, zar ne? Hajde da vidimo kako to izgleda u praksi.

Metoda apply(): Primjena funkcija po redovima i stupcima

apply() je najsvestraniji alat u vašem arsenalu. Funkcionira i na Series objektima i na cijelim DataFrame-ovima, a ključna razlika leži u parametru axis.

apply() na Series-u — element po element

Kad pozovete apply() na jednom stupcu (Series), funkcija se primjenjuje na svaki element zasebno. Evo klasičnog primjera s kategorizacijom:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "ime": ["Ana", "Marko", "Petra", "Ivan", "Lucija"],
    "dob": [28, 35, 42, 23, 31],
    "placa": [5500, 7200, 8100, 4300, 6800]
})

# Kategorizacija dobi pomoću vlastite funkcije
def dobna_skupina(dob):
    if dob < 25:
        return "mladi"
    elif dob < 35:
        return "srednja dob"
    else:
        return "stariji"

df["skupina"] = df["dob"].apply(dobna_skupina)
print(df)

Rezultat:

      ime  dob  placa      skupina
0     Ana   28   5500  srednja dob
1   Marko   35   7200      stariji
2   Petra   42   8100      stariji
3    Ivan   23   4300       mladi
4  Lucija   31   6800  srednja dob

Prilično čitljivo, zar ne? Definirali smo funkciju, predali je u apply(), i gotovo.

apply() na DataFrame-u — po redovima ili stupcima

Kada koristite apply() na DataFrame-u, parametar axis određuje smjer primjene:

  • axis=0 (zadano) — funkcija se primjenjuje na svaki stupac
  • axis=1 — funkcija se primjenjuje na svaki red

Ovo je nešto što zna zbuniti početnike (pa i iskusnije korisnike, moram priznati), pa evo konkretnog primjera za obje varijante:

# Primjena po stupcima (axis=0) — prosjek numeričkih stupaca
prosjeci = df[["dob", "placa"]].apply(np.mean, axis=0)
print(prosjeci)
# dob       31.8
# placa    6380.0

# Primjena po redovima (axis=1) — kombinirani opis svakog zaposlenika
df["opis"] = df.apply(
    lambda red: f"{red['ime']} ({red['dob']} god.) - plaća {red['placa']} EUR",
    axis=1
)
print(df["opis"])
# 0      Ana (28 god.) - plaća 5500 EUR
# 1    Marko (35 god.) - plaća 7200 EUR
# ...

apply() s dodatnim argumentima

Funkciji unutar apply() možete proslijediti dodatne argumente pomoću parametara args i **kwargs. Ovo je korisno kad želite parametrizirati transformaciju bez pisanja nove funkcije svaki put:

# Funkcija s dodatnim parametrom — postotak povišice
def izracunaj_novu_placu(placa, postotak):
    return round(placa * (1 + postotak / 100), 2)

df["nova_placa"] = df["placa"].apply(izracunaj_novu_placu, args=(10,))
print(df[["ime", "placa", "nova_placa"]])
#       ime  placa  nova_placa
# 0     Ana   5500      6050.0
# 1   Marko   7200      7920.0
# 2   Petra   8100      8910.0
# 3    Ivan   4300      4730.0
# 4  Lucija   6800      7480.0

Metoda map(): Mapiranje i transformacija element po element

map() je dizajniran za transformacije element po element i ima jednu veliku prednost nad apply() — osim funkcija, prima i rječnike te Series objekte kao argument. Po mom iskustvu, upravo ta mogućnost mapiranja rječnikom je nešto što ćete koristiti iznimno često.

map() s rječnikom — zamjena vrijednosti

# Mapiranje kategorija pomoću rječnika
gradovi = {
    "Ana": "Zagreb",
    "Marko": "Split",
    "Petra": "Rijeka",
    "Ivan": "Osijek",
    "Lucija": "Zagreb"
}

df["grad"] = df["ime"].map(gradovi)
print(df[["ime", "grad"]])
#       ime    grad
# 0     Ana  Zagreb
# 1   Marko   Split
# 2   Petra  Rijeka
# 3    Ivan  Osijek
# 4  Lucija  Zagreb

Ovako jednostavno. Nema petlji, nema if-else lanca — samo rječnik i map().

map() s funkcijom

# Formatiranje plaće s valutom
df["placa_fmt"] = df["placa"].map(lambda x: f"{x:,.0f} EUR")
print(df[["ime", "placa_fmt"]])
#       ime placa_fmt
# 0     Ana 5,500 EUR
# 1   Marko 7,200 EUR
# 2   Petra 8,100 EUR
# 3    Ivan 4,300 EUR
# 4  Lucija 6,800 EUR

map() vs apply() na Series-u — kada koristiti što?

Za jednostavne transformacije na jednom stupcu, map() je obično brži od apply(). Na testovima s milijun redaka, razlika može biti i do 10 puta. To je značajno.

Evo kratkih smjernica:

  • Koristite map() za: zamjenu vrijednosti rječnikom, jednostavne transformacije jednog stupca, mapiranje kategorija
  • Koristite apply() za: složenu logiku koja zahtijeva pristup cijelom redu (axis=1), funkcije s dodatnim argumentima, operacije po stupcima na DataFrame-u

DataFrame.map() — nasljednica zastarjele applymap()

Ovo je bitna promjena koju mnogi propuštaju. U pandas verziji 2.1.0, metoda applymap() je označena kao zastarjela (deprecated) i zamijenjena metodom DataFrame.map(). U pandas 3.x (aktualna verzija 2026. godine) trebali biste koristiti isključivo DataFrame.map():

# Primjena funkcije na svaki element DataFrame-a
numericke_kolone = df[["dob", "placa"]]

# STARI način (zastarjelo, izbjegavajte):
# numericke_kolone.applymap(lambda x: x * 1.1)

# NOVI način (pandas 3.x):
rezultat = numericke_kolone.map(lambda x: round(x * 1.1, 2))
print(rezultat)
#     dob    placa
# 0  30.8   6050.0
# 1  38.5   7920.0
# 2  46.2   8910.0
# 3  25.3   4730.0
# 4  34.1   7480.0

Napomena: Ako radite na starijem projektu koji još koristi applymap(), pandas 3.x će podići grešku — ne upozorenje, nego grešku. Jednostavno zamijenite sve pozive applymap() s map() i sve će raditi jer je ponašanje identično.

Lambda funkcije — brze anonimne transformacije

Lambda funkcije su, jednostavno rečeno, bezimene funkcije koje definirate u jednoj liniji koda. U kombinaciji s apply() i map(), omogućuju vam pisanje transformacija bez prethodnog definiranja zasebne funkcije. Idealne su za one "brze" operacije koje ne zaslužuju cijelu def definiciju.

Osnovna sintaksa lambda funkcija

# Sintaksa: lambda argumenti: izraz
kvadrat = lambda x: x ** 2
print(kvadrat(5))  # 25

# Ekvivalentna obična funkcija:
def kvadrat_func(x):
    return x ** 2

Lambda s uvjetnom logikom

# Kategorizacija plaća pomoću lambda izraza
df["razina_place"] = df["placa"].apply(
    lambda x: "visoka" if x >= 7000 else ("srednja" if x >= 5000 else "niska")
)
print(df[["ime", "placa", "razina_place"]])
#       ime  placa razina_place
# 0     Ana   5500      srednja
# 1   Marko   7200       visoka
# 2   Petra   8100       visoka
# 3    Ivan   4300        niska
# 4  Lucija   6800      srednja

Kada NE koristiti lambda?

Ovo je važno. Lambda izrazi su praktični za kratku logiku, ali postaju nečitljivi čim ih previše zakomplicirate. Moje pravilo palca: ako vaš lambda ima više od jednog uvjeta ili prelazi 80 znakova, definirajte običnu funkciju. Čitljivost koda je važnija nego ušteda na jednoj liniji.

# ❌ Nečitljiv lambda — izbjegavajte
df["kategorija"] = df.apply(
    lambda r: "senior visoka" if r["dob"] > 40 and r["placa"] > 7000 else ("junior niska" if r["dob"] < 25 and r["placa"] < 5000 else "ostali"),
    axis=1
)

# ✅ Čitljiva imenovana funkcija — preporučeno
def kategoriziraj_zaposlenika(red):
    if red["dob"] > 40 and red["placa"] > 7000:
        return "senior visoka"
    elif red["dob"] < 25 and red["placa"] < 5000:
        return "junior niska"
    else:
        return "ostali"

df["kategorija"] = df.apply(kategoriziraj_zaposlenika, axis=1)

Razlika u čitljivosti je ogromna, a debugiranje imenovane funkcije je neusporedivo lakše.

Metoda pipe(): Čisto ulančavanje transformacija

Sad dolazimo do nečeg što dosta ljudi ne koristi dovoljno. Metoda pipe() omogućuje vam ulančavanje prilagođenih funkcija u čist, čitljiv pipeline. Umjesto ugniježđenih poziva funkcija (koji brzo postanu nečitljivi), svaki korak teče od vrha prema dnu:

# Definicija koraka pipeline-a
def ukloni_duplikate(df):
    return df.drop_duplicates()

def dodaj_bonus(df, postotak=5):
    df = df.copy()
    df["bonus"] = df["placa"] * postotak / 100
    return df

def oznaci_visoke_place(df, prag=7000):
    df = df.copy()
    df["visoka_placa"] = df["placa"] >= prag
    return df

# Ulančavanje s pipe() — čisto i čitljivo
rezultat = (
    df.pipe(ukloni_duplikate)
      .pipe(dodaj_bonus, postotak=10)
      .pipe(oznaci_visoke_place, prag=6500)
)
print(rezultat[["ime", "placa", "bonus", "visoka_placa"]])
#       ime  placa  bonus  visoka_placa
# 0     Ana   5500  550.0         False
# 1   Marko   7200  720.0          True
# 2   Petra   8100  810.0          True
# 3    Ivan   4300  430.0         False
# 4  Lucija   6800  680.0          True

Prednost pipe() posebno dolazi do izražaja u produkcijskim pipeline-ovima gdje imate 5, 10 ili više koraka obrade. Svaki korak je zasebna funkcija koju možete testirati neovisno — a to je ogromna prednost kad nešto pođe po krivu.

Performanse: Kada izbjegavati apply()

Sad ću biti iskren — ovo je dio koji mnogi vodiči preskaču, a po meni je jedan od najvažnijih. Metoda apply() je spora. Interno prolazi kroz Python petlju red po red, što znači da za velike skupove podataka (stotine tisuća ili milijuni redaka) razlika u brzini može biti ogromna. Govorimo o 10 do 100 puta sporijoj izvedbi u usporedbi s vektoriziranim operacijama.

Hijerarhija brzine

Evo smjernice od najbržeg do najsporijeg pristupa:

  1. Vektorizirane NumPy/pandas operacije — uvijek prvi izbor
  2. np.where() / np.select() — za uvjetnu logiku
  3. .str pristupnik — za operacije na stringovima
  4. map() — brže od apply za elementwise operacije
  5. apply() — zadnji izbor, samo kad nema alternative

Praktični primjeri zamjene apply()-a

Pogledajmo konkretne primjere kako zamijeniti spori apply() bržim alternativama:

import numpy as np

# Generiramo veći DataFrame za testiranje
veliki_df = pd.DataFrame({
    "vrijednost": np.random.randint(0, 100, size=100_000),
    "kategorija": np.random.choice(["A", "B", "C"], size=100_000)
})

# ❌ SPORO — apply s lambda
veliki_df["duplo"] = veliki_df["vrijednost"].apply(lambda x: x * 2)

# ✅ BRZO — vektorizirana operacija
veliki_df["duplo"] = veliki_df["vrijednost"] * 2

# ❌ SPORO — uvjetna logika s apply
veliki_df["oznaka"] = veliki_df["vrijednost"].apply(
    lambda x: "visoko" if x > 75 else ("srednje" if x > 25 else "nisko")
)

# ✅ BRZO — np.select za uvjetnu logiku
uvjeti = [
    veliki_df["vrijednost"] > 75,
    veliki_df["vrijednost"] > 25
]
izbori = ["visoko", "srednje"]
veliki_df["oznaka"] = np.select(uvjeti, izbori, default="nisko")

Mjerenje performansi

Ako me ne vjerujete na riječ (i ne biste trebali — uvijek mjerite!), evo kako sami možete provjeriti:

import time

# Benchmark: apply vs vektorizacija
start = time.time()
veliki_df["rezultat_apply"] = veliki_df["vrijednost"].apply(lambda x: x ** 2 + x * 3)
trajanje_apply = time.time() - start

start = time.time()
veliki_df["rezultat_vektor"] = veliki_df["vrijednost"] ** 2 + veliki_df["vrijednost"] * 3
trajanje_vektor = time.time() - start

print(f"apply():        {trajanje_apply:.4f} sekundi")
print(f"vektorizirano:  {trajanje_vektor:.4f} sekundi")
print(f"Ubrzanje:       {trajanje_apply / trajanje_vektor:.1f}x")
# Tipičan rezultat: vektorizacija je 20-50x brža

Razlika je drastična. Na 100.000 redaka to je par sekundi, ali na 10 milijuna redaka — to je razlika između "gotovo odmah" i "idemo na kavu dok se ovo izvrti".

Praktični primjer: Kompletna obrada podataka o zaposlenicima

Hajde da sad spojimo sve naučeno u jednom realističnom primjeru. Cilj je pokazati cijeli tijek obrade podataka i kako za svaki korak odabrati pravi alat:

import pandas as pd
import numpy as np

# Sirovi podaci o zaposlenicima
podaci = pd.DataFrame({
    "ime": ["  Ana Horvat ", "marko KOVAČEVIĆ", "PETRA novak", "ivan BABIĆ", "lucija Jurić"],
    "odjel": ["IT", "Marketing", "IT", "Financije", "Marketing"],
    "placa_bruto": [8500, 7200, 9100, 6300, 7800],
    "datum_zaposlenja": ["2019-03-15", "2021-07-01", "2018-11-20", "2023-01-10", "2020-06-05"],
    "ocjena_rada": [4.2, 3.8, 4.7, 3.5, 4.0]
})

# Korak 1: Čišćenje imena — .str pristupnik (vektorizirano)
podaci["ime"] = podaci["ime"].str.strip().str.title()

# Korak 2: Konverzija datuma
podaci["datum_zaposlenja"] = pd.to_datetime(podaci["datum_zaposlenja"])

# Korak 3: Izračun staža u godinama (vektorizirano)
podaci["staz_godine"] = ((pd.Timestamp.now() - podaci["datum_zaposlenja"]).dt.days / 365.25).round(1)

# Korak 4: Neto plaća — vektorizirana operacija s np.where
porezna_stopa = np.where(podaci["placa_bruto"] > 8000, 0.35, 0.30)
podaci["placa_neto"] = (podaci["placa_bruto"] * (1 - porezna_stopa)).round(2)

# Korak 5: Kategorija učinkovitosti — np.select
uvjeti = [
    podaci["ocjena_rada"] >= 4.5,
    podaci["ocjena_rada"] >= 3.5
]
kategorije = ["izvrsna", "dobra"]
podaci["ucinkovitost"] = np.select(uvjeti, kategorije, default="potrebno poboljšanje")

# Korak 6: Složeni izračun bonusa — apply jer ovisi o više stupaca
#           i ima složenu poslovnu logiku
def izracunaj_bonus(red):
    bazni_bonus = red["placa_bruto"] * 0.10
    if red["ocjena_rada"] >= 4.5:
        bazni_bonus *= 1.5
    if red["staz_godine"] >= 5:
        bazni_bonus *= 1.2
    return round(bazni_bonus, 2)

podaci["bonus"] = podaci.apply(izracunaj_bonus, axis=1)

# Korak 7: Mapiranje odjela na kratice
kratice_odjela = {"IT": "IT", "Marketing": "MKT", "Financije": "FIN"}
podaci["odjel_kratica"] = podaci["odjel"].map(kratice_odjela)

print(podaci[["ime", "odjel_kratica", "placa_neto", "staz_godine", "ucinkovitost", "bonus"]])
#                  ime odjel_kratica  placa_neto  staz_godine ucinkovitost    bonus
# 0        Ana Horvat            IT     5525.00          7.0        dobra  1020.00
# 1  Marko Kovačević           MKT     5040.00          4.7        dobra   720.00
# 2      Petra Novak            IT     5915.00          7.3      izvrsna  1638.00
# 3       Ivan Babić           FIN     4410.00          3.2        dobra   630.00
# 4     Lucija Jurić           MKT     5460.00          5.8        dobra   936.00

Primijetite obrazac: za svaki korak odabrali smo optimalan pristup. Vektorizirane operacije gdje god je moguće, map() za jednostavno mapiranje, a apply() samo za onu složenu poslovnu logiku bonusa koja ovisi o više stupaca i sadrži ugniježđene uvjete. U praksi, upravo ovakav pristup daje najbolji balans između čitljivosti i performansi.

Brza referenca: Tablica usporedbe metoda

MetodaRadi naPrimaNajbolja zaBrzina
Series.apply()SeriesFunkcijuSloženu logiku na jednom stupcuSpora
DataFrame.apply(axis=0)DataFrameFunkcijuOperacije po stupcimaSpora
DataFrame.apply(axis=1)DataFrameFunkcijuOperacije po redovima (više stupaca)Najsporija
Series.map()SeriesFunkciju, rječnik, SeriesZamjena/transformacija vrijednostiBrza
DataFrame.map()DataFrameFunkcijuElementwise transformacijeSrednja
pipe()DataFrame/SeriesFunkcijuUlančavanje koraka pipeline-aOvisi o funkciji

Često postavljana pitanja (FAQ)

Koja je razlika između apply() i map() u pandas-u?

apply() je svestranija — radi i na Series-u i na DataFrame-u, te može primjenjivati funkcije po redovima (axis=1) ili stupcima (axis=0). S druge strane, map() na Series-u je specijalizirana za transformacije element po element i može primati rječnike i Series objekte kao argument, ne samo funkcije. Za jednostavne transformacije jednog stupca, map() je brži izbor.

Je li applymap() zastarjela i što koristiti umjesto nje?

Da, definitivno. applymap() je označena kao zastarjela od pandas verzije 2.1.0 i potpuno uklonjena u pandas 3.0. Umjesto nje koristite DataFrame.map() koja ima identično ponašanje — primjenjuje funkciju na svaki element DataFrame-a.

Zašto je pandas apply() spor i kako ga ubrzati?

apply() je spor jer interno koristi Python petlju umjesto optimiziranih C operacija koje pokreću vektorizirane pandas/NumPy funkcije. Za ubrzavanje: zamijenite aritmetičke operacije vektoriziranim izrazima (npr. df["a"] * 2), uvjetnu logiku s np.where() ili np.select(), a string operacije s .str pristupnikom.

Kada je bolje koristiti apply() umjesto vektoriziranih operacija?

Koristite apply() kada logika zahtijeva pristup više stupaca istovremeno (axis=1), kada pozivate vanjske biblioteke ili API-je, ili kada je poslovna logika presložena za izražavanje vektoriziranim operacijama. Za male skupove podataka (manje od 10.000 redaka), razlika u performansama je zanemariva, pa čitljivost koda može imati prednost.

Kako koristiti pipe() za čišći pandas kod?

pipe() omogućuje ulančavanje prilagođenih funkcija u čitljiv pipeline. Definirajte svaki korak obrade kao zasebnu funkciju koja prima DataFrame i vraća DataFrame, a zatim ih povežite s .pipe(). Svaka funkcija može imati vlastite parametre, lako se testira zasebno, i čitav pipeline teče od vrha prema dnu.

O Autoru Editorial Team

Our team of expert writers and editors.