Ako ste pratili našu seriju o pandas biblioteci — od čišćenja podataka, preko GroupBy operacija, do spajanja DataFrame-ova — onda već znate koliko posla ide u pripremu podataka prije nego što uopće dođete do zanimljivog dijela. Ali tu priča ne staje. Postoji još jedan korak koji vas čeka: preoblikovanje podataka.
Zvuči možda apstraktno, ali radi se o nečemu sasvim konkretnom — pretvaranju podataka iz jednog oblika u drugi. Iz širokog u dugi format, i obrnuto. Bez te vještine, vizualizacija, analiza i priprema za strojno učenje postaju nepotrebno kompliciranije.
Hajdemo onda proći kroz sve alate koje pandas nudi za ovo: pivot(), pivot_table(), melt(), stack() i unstack(). Svaki ćemo popratiti primjerima s radnim kodom, a dotaknut ćemo se i promjena u pandas 3.0 koje utječu na ove operacije.
Široki i dugi format podataka
Prije nego skočimo na konkretne funkcije, moramo razjasniti jednu temeljnu stvar. Razlika između širokog (wide) i dugog (long) formata podataka ključna je za sve što slijedi.
Široki format izgleda kao tipična Excel tablica — svaki atribut ima svoj stupac. Recimo da pratite prodaju po kvartalima, pa svaki kvartal dobije vlastiti stupac:
import pandas as pd
import numpy as np
# Široki format — svaki kvartal je zaseban stupac
prodaja_siroki = pd.DataFrame({
'regija': ['Zagreb', 'Split', 'Rijeka', 'Osijek'],
'Q1_2025': [15000, 12000, 8000, 9500],
'Q2_2025': [17000, 13500, 8500, 10000],
'Q3_2025': [16000, 14000, 9000, 11000],
'Q4_2025': [19000, 15000, 9500, 12000]
})
print(prodaja_siroki)
Dugi format pohranjuje iste podatke, ali na drugačiji način — svaka opservacija zauzima jedan redak. Kvartali više nisu stupci, nego vrijednosti u jednom stupcu:
# Dugi format — svi kvartali u jednom stupcu
prodaja_dugi = pd.DataFrame({
'regija': ['Zagreb', 'Zagreb', 'Zagreb', 'Zagreb',
'Split', 'Split', 'Split', 'Split'],
'kvartal': ['Q1_2025', 'Q2_2025', 'Q3_2025', 'Q4_2025',
'Q1_2025', 'Q2_2025', 'Q3_2025', 'Q4_2025'],
'prodaja': [15000, 17000, 16000, 19000,
12000, 13500, 14000, 15000]
})
print(prodaja_dugi)
Oba formata sadrže iste informacije. Razlika je u tome za što su pogodni.
Široki format je čitljiviji za ljude i super je za izvještaje. Dugi format pak preferiraju vizualizacijske biblioteke poput seaborna i matplotliba, a bolji je i za statističke modele. Upravo zato trebamo alate za pretvaranje iz jednog formata u drugi — i tu dolaze pandas funkcije o kojima ćemo pričati.
pivot() — Osnovno preoblikovanje bez agregacije
Funkcija pivot() najjednostavniji je način za pretvaranje dugog formata u široki. Princip je jasan: uzimate vrijednosti iz jednog stupca i pretvarate ih u nazive novih stupaca.
# Pretvaramo dugi format u široki pomoću pivot()
siroki = prodaja_dugi.pivot(
index='regija',
columns='kvartal',
values='prodaja'
)
print(siroki)
Rezultat je tablica u kojoj su regije redci, kvartali stupci, a vrijednosti su iznosi prodaje. Čisto i pregledno.
Ali — i ovo je bitno — pivot() zahtijeva da kombinacija indeksa i stupca bude jedinstvena. Ako u vašim podacima postoje duplikati (recimo, dva zapisa za Zagreb u Q1), dobit ćete grešku:
# Ovo će baciti grešku jer postoje duplikati
podaci_s_duplikatima = pd.DataFrame({
'regija': ['Zagreb', 'Zagreb', 'Split'],
'kvartal': ['Q1', 'Q1', 'Q1'],
'prodaja': [15000, 16000, 12000]
})
try:
podaci_s_duplikatima.pivot(index='regija', columns='kvartal', values='prodaja')
except ValueError as e:
print(f"Greška: {e}")
Kada imate duplikate, trebate nešto moćnije — pivot_table().
pivot_table() — Preoblikovanje s agregacijom
Iskreno, pivot_table() je funkcija koju ćete u praksi koristiti mnogo češće od običnog pivot(). Radi na sličan način, ali može obraditi duplikate primjenjujući agregacijske funkcije — zbroj, prosjek, broj pojavljivanja, što god trebate. Ako ste ikada koristili pivot tablice u Excelu, ovo je isti koncept, samo u Pythonu.
Napravimo nešto složeniji primjer s podacima o prodaji:
# Detaljni podaci o prodaji
prodaja = pd.DataFrame({
'regija': ['Zagreb', 'Zagreb', 'Zagreb', 'Split', 'Split', 'Split',
'Rijeka', 'Rijeka', 'Rijeka', 'Zagreb', 'Split', 'Rijeka'],
'kategorija': ['Elektronika', 'Odjeća', 'Elektronika', 'Odjeća',
'Elektronika', 'Hrana', 'Hrana', 'Elektronika', 'Odjeća',
'Hrana', 'Hrana', 'Elektronika'],
'iznos': [5000, 3000, 7000, 2500, 4500, 1800,
2200, 3800, 1500, 1200, 2000, 4200],
'kolicina': [10, 25, 15, 30, 8, 50,
45, 12, 20, 60, 55, 9]
})
# Osnovna pivot tablica — prosjek je zadana agregacija
tablica = pd.pivot_table(
prodaja,
values='iznos',
index='regija',
columns='kategorija'
)
print("Prosječni iznos po regiji i kategoriji:")
print(tablica)
Različite agregacijske funkcije
Zadana agregacija je mean (prosjek), ali tu niste ograničeni. Možete koristiti sum, count, min, max ili čak vlastite funkcije:
# Ukupna prodaja (zbroj) po regiji i kategoriji
tablica_zbroj = pd.pivot_table(
prodaja,
values='iznos',
index='regija',
columns='kategorija',
aggfunc='sum',
fill_value=0
)
print("Ukupna prodaja:")
print(tablica_zbroj)
# Više agregacijskih funkcija odjednom
tablica_vise = pd.pivot_table(
prodaja,
values='iznos',
index='regija',
columns='kategorija',
aggfunc=['sum', 'mean', 'count'],
fill_value=0
)
print("\nVišestruka agregacija:")
print(tablica_vise)
Različite funkcije za različite stupce
Ovo je po meni jedna od najkorisnijih mogućnosti pivot_table() — možete primijeniti različite agregacijske funkcije na različite stupce vrijednosti:
# Zbroj iznosa, ali prosjek količine
tablica_mix = pd.pivot_table(
prodaja,
values=['iznos', 'kolicina'],
index='regija',
columns='kategorija',
aggfunc={'iznos': 'sum', 'kolicina': 'mean'},
fill_value=0
)
print(tablica_mix)
Dodavanje ukupnih zbroja s margins
Parametar margins=True dodaje redak i stupac s ukupnim zbrojevima. U biti, to je ekvivalent Grand Total opcije u Excelu:
# Pivot tablica s ukupnim zbrojevima
tablica_s_ukupnim = pd.pivot_table(
prodaja,
values='iznos',
index='regija',
columns='kategorija',
aggfunc='sum',
fill_value=0,
margins=True,
margins_name='Ukupno'
)
print(tablica_s_ukupnim)
Rezultat sadrži redak i stupac Ukupno koji prikazuju sumu po svakoj regiji i kategoriji. Savršeno za izvještaje koje šaljete nekome tko ne želi gledati sirovi kod.
Višerazinski indeks
Možete koristiti više stupaca kao indeks ili stupce za stvaranje složenijih pivot tablica. Ovo postaje posebno korisno kad radite s više dimenzija podataka:
# Dodajmo podatke o kvartalu
prodaja['kvartal'] = ['Q1', 'Q1', 'Q2', 'Q1', 'Q2', 'Q1',
'Q2', 'Q1', 'Q2', 'Q2', 'Q2', 'Q1']
# Višerazinski pivot
tablica_visera = pd.pivot_table(
prodaja,
values='iznos',
index=['regija', 'kvartal'],
columns='kategorija',
aggfunc='sum',
fill_value=0
)
print(tablica_visera)
melt() — Pretvaranje iz širokog u dugi format
E sad, melt() radi upravo suprotno od pivot() — pretvara široki format u dugi. Uzima stupce koji predstavljaju vrijednosti i „otapa" ih u redke. Sam naziv dolazi od analogije s topljenjem — zamislite da se stupci „tope" i postaju redci. Zgodna metafora, zar ne?
# Počinjemo sa širokim formatom
prodaja_siroki = pd.DataFrame({
'regija': ['Zagreb', 'Split', 'Rijeka'],
'Q1': [15000, 12000, 8000],
'Q2': [17000, 13500, 8500],
'Q3': [16000, 14000, 9000],
'Q4': [19000, 15000, 9500]
})
# Pretvaramo u dugi format
prodaja_dugi = pd.melt(
prodaja_siroki,
id_vars=['regija'],
value_vars=['Q1', 'Q2', 'Q3', 'Q4'],
var_name='kvartal',
value_name='prodaja'
)
print(prodaja_dugi)
Parametri su zapravo prilično intuitivni kad ih jednom shvatite:
- id_vars — stupci koji ostaju nepromijenjeni (vaši identifikatori)
- value_vars — stupci koji se „otapaju" u redke (ako ih ne navedete, koriste se svi koji nisu u id_vars)
- var_name — naziv novog stupca koji sadrži bivše nazive stupaca
- value_name — naziv novog stupca s vrijednostima
Melt s više identifikatora
Naravno, možete zadržati i više stupaca kao identifikatore:
# Tablica s više informacija
prihodi = pd.DataFrame({
'tvrtka': ['Tvrtka A', 'Tvrtka B'],
'sektor': ['IT', 'Proizvodnja'],
'prihod_2024': [500000, 800000],
'prihod_2025': [600000, 750000],
'zaposlenici_2024': [50, 120],
'zaposlenici_2025': [65, 110]
})
# Otapamo samo stupce s prihodima
prihodi_dugi = pd.melt(
prihodi,
id_vars=['tvrtka', 'sektor'],
value_vars=['prihod_2024', 'prihod_2025'],
var_name='godina',
value_name='prihod'
)
print(prihodi_dugi)
Melt kao priprema za vizualizaciju
Jedan od najčešćih razloga zašto uopće koristimo melt() je priprema podataka za vizualizaciju. Seaborn, primjerice, jako preferira dugi format — i bez njega biste morali pisati puno više koda za iste grafikone:
import matplotlib.pyplot as plt
# Dugi format je idealan za seaborn
dugi = pd.melt(
prodaja_siroki,
id_vars=['regija'],
var_name='kvartal',
value_name='prodaja'
)
# Sada možemo lako napraviti grupirani stupčasti grafikon
dugi.pivot(index='regija', columns='kvartal', values='prodaja').plot(
kind='bar', figsize=(10, 6)
)
plt.title('Prodaja po regijama i kvartalima')
plt.ylabel('Iznos (EUR)')
plt.xlabel('Regija')
plt.legend(title='Kvartal')
plt.tight_layout()
plt.show()
stack() i unstack() — Rad s MultiIndex-om
Funkcije stack() i unstack() rade s hijerarhijskim (višerazinskim) indeksima. Konceptualno su slične melt() i pivot(), ali operiraju na razinama indeksa umjesto na stupcima. U praksi, koristit ćete ih rjeđe od melt i pivot_table, ali kad vam zatrebaju — nezamjenjive su.
stack() — Stupci postaju redci
stack() uzima razinu stupaca i pretvara je u razinu indeksa redaka. Pojednostavljeno — komprimira široki format u dugi:
# Krećemo od pivot tablice
tablica = prodaja_siroki.set_index('regija')
print("Prije stack():")
print(tablica)
print()
# Primjenjujemo stack
složeni = tablica.stack()
print("Nakon stack():")
print(složeni)
Rezultat je Series s MultiIndex-om — regija na prvoj razini, kvartal na drugoj.
unstack() — Redci postaju stupci
unstack() radi obrnuto — uzima razinu indeksa i pretvara je natrag u stupce:
# Vraćamo podatke u široki format
rašireni = složeni.unstack()
print("Nakon unstack():")
print(rašireni)
Korisna stvar je što možete kontrolirati koju razinu indeksa pretvarate pomoću parametra level:
# Unstack prve razine umjesto zadnje
rašireni_regija = složeni.unstack(level=0)
print("Unstack po regiji:")
print(rašireni_regija)
Oprez s nedostajućim vrijednostima
Evo nečega na što sam se sam jednom opekao: stack() prema zadanim postavkama tiho uklanja redke s nedostajućim vrijednostima. Ako vam je bitno sačuvati NaN vrijednosti (a ponekad jest), obavezno koristite dropna=False:
# Podaci s nedostajućim vrijednostima
df = pd.DataFrame({
'A': [1, np.nan, 3],
'B': [4, 5, np.nan]
}, index=['x', 'y', 'z'])
# Bez dropna — NaN redci se uklanjaju
print("stack() bez dropna:")
print(df.stack())
print()
# S dropna=False — svi redci se čuvaju
print("stack(dropna=False):")
print(df.stack(dropna=False))
Praktični primjer: Analiza mjesečne prodaje
Dosta teorije — pogledajmo sad jedan realniji scenarij koji kombinira više tehnika preoblikovanja. Zamislite da imate podatke o mjesečnoj prodaji različitih proizvoda u više trgovina:
# Simuliramo podatke o prodaji
np.random.seed(42)
mjeseci = ['Siječanj', 'Veljača', 'Ožujak', 'Travanj', 'Svibanj', 'Lipanj']
trgovine = ['Trgovina A', 'Trgovina B', 'Trgovina C']
proizvodi = ['Laptop', 'Mobitel', 'Tablet']
zapisi = []
for trgovina in trgovine:
for proizvod in proizvodi:
for mjesec in mjeseci:
zapisi.append({
'trgovina': trgovina,
'proizvod': proizvod,
'mjesec': mjesec,
'prodaja': np.random.randint(5, 50),
'prihod': np.random.randint(1000, 20000)
})
df = pd.DataFrame(zapisi)
print(f"Ukupno zapisa: {len(df)}")
print(df.head(10))
Sad možemo koristiti pivot_table() za stvaranje izvještaja koji zapravo nešto znače:
# Izvještaj: Ukupni prihod po trgovini i proizvodu
izvjestaj = pd.pivot_table(
df,
values='prihod',
index='trgovina',
columns='proizvod',
aggfunc='sum',
margins=True,
margins_name='Ukupno'
)
print("Ukupni prihod po trgovini i proizvodu:")
print(izvjestaj)
# Izvještaj: Prosječna prodaja po mjesecu
mjesecni = pd.pivot_table(
df,
values='prodaja',
index='mjesec',
columns='trgovina',
aggfunc='mean'
).round(1)
print("\nProsječna prodaja po mjesecu i trgovini:")
print(mjesecni)
Novosti u pandas 3.0 relevantne za preoblikovanje
Pandas 3.0, objavljen početkom 2026., donio je nekoliko promjena koje direktno utječu na rad s preoblikovanjem podataka. Vrijedi ih imati na radaru.
Copy-on-Write je sada zadano ponašanje
Ovo je vjerojatno najveća promjena u pandas 3.0 — Copy-on-Write (CoW) je sada zadani i jedini način rada. Što to znači za vas? Sve operacije preoblikovanja — pivot(), melt(), stack(), unstack() — uvijek vraćaju novu kopiju podataka. Nema više zbunjujućih situacija oko toga radi li se o pogledu (view) ili kopiji:
# U pandas 3.0, ovo je uvijek sigurno
rezultat = df.pivot_table(values='prihod', index='trgovina', columns='proizvod', aggfunc='sum')
rezultat.iloc[0, 0] = 999 # Ne mijenja originalni df
Iskreno, ovo je promjena koju sam osobno dočekao s olakšanjem. Koliko sam puta morao razmišljati „hoće li ovo modificirati originalni DataFrame?" — sad je odgovor uvijek jasan.
Novi pd.col() za čišći kod
Nova pd.col() sintaksa pojednostavljuje operacije nakon preoblikovanja. Umjesto da pišete lambda funkcije, možete referirati stupce izravno:
# Umjesto lambda funkcija, koristimo pd.col()
rezultat = df.assign(
prihod_po_komadu=pd.col('prihod') / pd.col('prodaja')
)
print(rezultat.head())
Dedicated string dtype
U pandas 3.0, tekstualni stupci se automatski prepoznaju kao str tip umjesto object. Ovo utječe i na melt() — stupac variable sada ima tip str umjesto object. Sitnica, ali čini tipove podataka preciznijima i rad s njima učinkovitijim.
Tablica usporedbe: Kada koristiti koju funkciju
Da ne morate pamtiti sve ovo napamet, evo kratkog pregleda:
- pivot() — iz dugog u široki format, bez duplikata, bez agregacije. Koristite kada su kombinacije indeksa i stupaca jedinstvene.
- pivot_table() — iz dugog u široki format, s agregacijom. Vaš go-to izbor kad imate duplikate ili trebate sumarne tablice.
- melt() — iz širokog u dugi format. Nezamjenjiv za pripremu podataka za vizualizaciju ili statističku analizu.
- stack() — stupci postaju razine indeksa. Za rad s MultiIndex strukturama.
- unstack() — razine indeksa postaju stupci. Za pretvaranje MultiIndex-a u široku tablicu.
Opće pravilo koje mi pomaže: ako trebam agregaciju, idem na pivot_table(). Za jednostavno preoblikovanje bez agregacije, pivot() ili melt(). A za rad s hijerarhijskim indeksima, stack() i unstack().
Često postavljana pitanja
Koja je razlika između pivot() i pivot_table() u pandasu?
Funkcija pivot() služi za jednostavno preoblikovanje podataka iz dugog u široki format i zahtijeva da kombinacija indeksa i stupaca bude jedinstvena — ne podržava agregaciju. S druge strane, pivot_table() može obraditi duplikate primjenom agregacijskih funkcija poput sum, mean ili count. Ako imate duplikate u podacima, pivot() će baciti grešku, dok će pivot_table() agregirati vrijednosti.
Kako pretvoriti podatke iz širokog u dugi format u pandasu?
Za pretvaranje širokog formata u dugi koristite funkciju pd.melt(). Definirajte stupce koji ostaju nepromijenjeni pomoću parametra id_vars, a stupce koje želite pretvoriti u redke pomoću value_vars. Alternativno, možete koristiti stack() ako radite s MultiIndex strukturom.
Može li pandas zamijeniti Excel pivot tablice?
Apsolutno. Pandas pivot_table() nudi sve mogućnosti Excel pivot tablica, a zapravo i više. Podržava višestruke agregacijske funkcije, višerazinske indekse, automatsko popunjavanje nedostajućih vrijednosti i programsko generiranje izvještaja. Najveća prednost je automatizacija — možete skriptirati izvještaje koji se generiraju sami, umjesto da svaki put ručno klikate po Excelu.
Što znači melt() u pandasu i kada ga koristiti?
Funkcija melt() pretvara široki format podataka u dugi format tako da stupce „otapa" u redke. Koristite je kada trebate pripremiti podatke za vizualizacijske biblioteke (poput seaborna), statističke modele koji očekuju dugi format, ili kada nazivi stupaca zapravo predstavljaju vrijednosti varijable — npr. stupci '2023', '2024', '2025' umjesto jednog stupca 'godina'.
Kako riješiti grešku „Index contains duplicate entries" kod pivot()?
Ova greška znači da vaši podaci sadrže duplikate za istu kombinaciju indeksa i stupaca. Najjednostavnije rješenje je koristiti pivot_table() umjesto pivot(), jer pivot_table() automatski agregira duplikate. Alternativno, možete ukloniti duplikate pomoću drop_duplicates() prije poziva pivot(), ili grupirati podatke s groupby() i agregirati ih ručno.