library(tidyverse)
library(janitor)Tjedan 3: Rad s podacima u tidyverse
Od sirovih podataka do analizi spremnog dataseta
Nakon ovog predavanja moći ćete
- Objasniti zašto je čišćenje i transformacija podataka najvažniji (i najdugotrajniji) korak u svakoj analizi.
- Koristiti
clean_names()za standardizaciju imena stupaca i prepoznati zašto je to važno za ponovljivost. - Koristiti
filter()za odabir redova po jednom ili više uvjeta, uključujući kombinacije logičkih operatora i rad s nedostajućim vrijednostima. - Koristiti
select()za odabir, preimenovanje i preuređivanje stupaca, uključujući pomoćne funkcije poputstarts_with(),ends_with()icontains(). - Koristiti
mutate()za kreiranje novih varijabli, transformaciju postojećih i rekodiranje vrijednosti pomoćucase_when()iif_else(). - Koristiti
arrange()za sortiranje podataka po jednom ili više stupaca u uzlaznom i silaznom redoslijedu. - Kombinirati dplyr glagole u pipeline koristeći pipe operator za složene transformacije podataka.
- Prepoznati tipične probleme u sirovim podacima (nekonzistentno kodiranje, mješoviti tipovi, nedostajuće vrijednosti) i primijeniti odgovarajuće strategije čišćenja.
1 Prljava tajna analize podataka
Postoji jedna stvar o kojoj vam udžbenici statistike rijetko govore. Otvorite bilo koji udžbenik i vidjet ćete poglavlje o t-testu, poglavlje o regresiji, poglavlje o ANOVA-i. Sve lijepo i uredno. Ali nitko vam ne kaže da ćete 80% vremena u bilo kojoj analizi provesti na nečemu što se ne pojavljuje ni u jednom od tih poglavlja. Radi se o čišćenju i pripremi podataka.
Ovo nije pretjerivanje. Stvarni podaci su gotovo uvijek neuredni. Anketa prikupljena putem Google Formsa dolazi s imenima stupaca poput “Koliko često pratite vijesti na društvenim mrežama? (odaberite jedan odgovor)”. Ispitanici u polje za spol upisuju “Ženski”, “ženski”, “Ž”, “female” i “Zensko”, a sve to treba biti ista kategorija. Stupac koji bi trebao sadržavati brojeve sadrži i tekst poput “ne gledam” ili prazne ćelije. Neki ispitanici imaju 19 godina, a jedan ima 199 jer mu je prst skliznuo na tipkovnici.
Sve ovo morate riješiti prije nego što možete izračunati ijedan prosjek ili napraviti ijedno testiranje hipoteza. I upravo zato je ovaj tjedan posvećen manipulaciji podacima. Naučit ćemo pet temeljnih funkcija iz paketa dplyr (filter(), select(), mutate(), summarise(), group_by()) plus alate za čišćenje i preoblikovanje iz paketa tidyr i janitor. Ove funkcije, spojene pipe operatorom u elegantne pipeline, čine okosnicu svake analize podataka u R-u.
Navarro u knjizi (poglavlja 4 i 7) pokriva sličan teren, ali u base R sintaksi. Mi ćemo koristiti tidyverse pristup koji je čitljiviji i konzistentniji. Kad ste jednom naučili logiku dplyr glagola, ista logika se primjenjuje na svaki dataset, svaki problem, svaku analizu.
2 Naši podaci: anketa o medijskim navikama studenata
Na ovom predavanju koristit ćemo simulirani dataset koji oponaša ono što biste zaista dobili kad biste proveli online anketu među studentima. Dataset je namjerno neuredan jer želimo vježbati čišćenje podataka na realističnom primjeru.
Učitajmo podatke i pogledajmo s čime se suočavamo.
raw <- read_csv("../resources/datasets/media_habits_raw.csv")
glimpse(raw)Rows: 250
Columns: 17
$ `ID respondenta` <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,…
$ Timestamp <dttm> 2025-03-28 17:05:00, 2025-04-20 2…
$ Dob <dbl> 20, 27, 27, 18, 25, 26, 28, 26, 22…
$ Spol <chr> "ženski", "Muški", "muški", "femal…
$ Grad <chr> "Zagreb", "Zadar", "Zagreb", "Spli…
$ `Godina studija` <chr> "2", "3", "1", "1", "2", "2.", "2"…
$ `TV (min/dan)` <chr> "0", "0", "65", NA, NA, "91", "91"…
$ `Portali (min/dan)` <dbl> 40, 20, 0, 11, 32, 25, 81, 28, 37,…
$ `Društvene mreže (min/dan)` <dbl> 59, 101, 177, 71, 161, 155, 114, 1…
$ `Radio (min/dan)` <dbl> 49, NA, 0, NA, 26, NA, 0, 0, 17, 0…
$ `Podcast (min/dan)` <dbl> 89, 0, 49, 0, 0, NA, NA, 31, 0, 19…
$ `Povjerenje TV (1-10)` <dbl> 2, 3, 4, 5, 5, 4, 3, 6, 6, 7, 2, 7…
$ `Povjerenje portali (1-10)` <dbl> 6, 5, 6, 6, 3, 7, 7, 1, 7, 5, 6, 5…
$ `Povjerenje društvene mreže (1-10)` <dbl> 4, 3, 1, 4, 4, 7, 2, 3, 4, 6, 1, 2…
$ `Broj platformi` <dbl> 9, 5, 7, 6, 5, 2, 1, 8, 5, 7, 6, 6…
$ `Koje platforme koristi` <chr> "Snapchat, WhatsApp, Facebook", "F…
$ `Koliko često prati vijesti` <chr> "više puta dnevno", "nekoliko puta…
Već na prvi pogled vidimo nekoliko problema. Imena stupaca sadrže razmake, zagrade i dijakritičke znakove, što otežava rad u R-u. Stupci poput Spol imaju nekonzistentne vrijednosti. Stupac TV (min/dan) sadrži i brojeve i tekst (“ne gledam”) i prazne ćelije, pa ga je R učitao kao tekst umjesto broja.
Pogledajmo prvih nekoliko redova detaljnije.
raw |>
head(10)# A tibble: 10 × 17
`ID respondenta` Timestamp Dob Spol Grad `Godina studija`
<dbl> <dttm> <dbl> <chr> <chr> <chr>
1 1 2025-03-28 17:05:00 20 ženski Zagreb 2
2 2 2025-04-20 21:11:00 27 Muški Zadar 3
3 3 2025-04-18 14:48:00 27 muški Zagreb 1
4 4 2025-03-28 12:54:00 18 female Split 1
5 5 2025-03-21 18:06:00 25 Ženski Zagreb 2
6 6 2025-04-14 20:26:00 26 M Zagreb 2.
7 7 2025-04-22 15:48:00 28 m Zagreb 2
8 8 2025-03-04 19:04:00 26 ženski Karlovac 2
9 9 2025-03-12 12:17:00 22 female Split 2
10 10 2025-03-19 18:17:00 21 ž Osijek 1
# ℹ 11 more variables: `TV (min/dan)` <chr>, `Portali (min/dan)` <dbl>,
# `Društvene mreže (min/dan)` <dbl>, `Radio (min/dan)` <dbl>,
# `Podcast (min/dan)` <dbl>, `Povjerenje TV (1-10)` <dbl>,
# `Povjerenje portali (1-10)` <dbl>,
# `Povjerenje društvene mreže (1-10)` <dbl>, `Broj platformi` <dbl>,
# `Koje platforme koristi` <chr>, `Koliko često prati vijesti` <chr>
Ovo je tipičan izgled sirovih podataka iz ankete. Prije bilo kakve analize, moramo napraviti čišćenje. Krenimo redom.
3 Korak nula: čišćenje imena stupaca
Prva stvar koju radimo sa svakim novim datasetom je standardizacija imena stupaca. Imena poput TV (min/dan) i Povjerenje društvene mreže (1-10) su problematična jer sadrže razmake, zagrade i specijalne znakove. Kad ih želite koristiti u kodu, morate ih stavljati u obrnute navodnike (poput `TV (min/dan)`). To je neugodno, nečitljivo i podložno greškama.
Paket janitor ima funkciju clean_names() koja automatski pretvara sva imena u snake_case format, uključujući mala slova, zamjenu razmaka podvlakama i uklanjanje specijalnih znakova.
raw <- raw |>
clean_names()
names(raw) [1] "id_respondenta" "timestamp"
[3] "dob" "spol"
[5] "grad" "godina_studija"
[7] "tv_min_dan" "portali_min_dan"
[9] "drustvene_mreze_min_dan" "radio_min_dan"
[11] "podcast_min_dan" "povjerenje_tv_1_10"
[13] "povjerenje_portali_1_10" "povjerenje_drustvene_mreze_1_10"
[15] "broj_platformi" "koje_platforme_koristi"
[17] "koliko_cesto_prati_vijesti"
Usporedite ova imena s originalnima. Umjesto Povjerenje društvene mreže (1-10) sada imamo povjerenje_drustvene_mreze_1_10. Duže jest, ali potpuno funkcionalno u R kodu bez ikakvih navodnika ili zagrada. Ovo je mala investicija koja štedi mnogo frustracije.
Navikajte se da clean_names() bude prva stvar koju pozovete nakon read_csv(). Možete to čak staviti u isti pipeline: raw <- read_csv("datoteka.csv") |> clean_names(). Ovo je toliko standardna praksa da mnogi R korisnici to rade automatski za svaki dataset, čak i kad su imena stupaca već uredna. Bolje spriječiti nego liječiti.
Sad kad imamo čista imena, možemo krenuti s pravim poslom. Pogledajmo strukturu nakon čišćenja.
glimpse(raw)Rows: 250
Columns: 17
$ id_respondenta <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,…
$ timestamp <dttm> 2025-03-28 17:05:00, 2025-04-20 21:11…
$ dob <dbl> 20, 27, 27, 18, 25, 26, 28, 26, 22, 21…
$ spol <chr> "ženski", "Muški", "muški", "female", …
$ grad <chr> "Zagreb", "Zadar", "Zagreb", "Split", …
$ godina_studija <chr> "2", "3", "1", "1", "2", "2.", "2", "2…
$ tv_min_dan <chr> "0", "0", "65", NA, NA, "91", "91", "0…
$ portali_min_dan <dbl> 40, 20, 0, 11, 32, 25, 81, 28, 37, 5, …
$ drustvene_mreze_min_dan <dbl> 59, 101, 177, 71, 161, 155, 114, 119, …
$ radio_min_dan <dbl> 49, NA, 0, NA, 26, NA, 0, 0, 17, 0, 0,…
$ podcast_min_dan <dbl> 89, 0, 49, 0, 0, NA, NA, 31, 0, 19, 0,…
$ povjerenje_tv_1_10 <dbl> 2, 3, 4, 5, 5, 4, 3, 6, 6, 7, 2, 7, 7,…
$ povjerenje_portali_1_10 <dbl> 6, 5, 6, 6, 3, 7, 7, 1, 7, 5, 6, 5, 5,…
$ povjerenje_drustvene_mreze_1_10 <dbl> 4, 3, 1, 4, 4, 7, 2, 3, 4, 6, 1, 2, 3,…
$ broj_platformi <dbl> 9, 5, 7, 6, 5, 2, 1, 8, 5, 7, 6, 6, 2,…
$ koje_platforme_koristi <chr> "Snapchat, WhatsApp, Facebook", "Faceb…
$ koliko_cesto_prati_vijesti <chr> "više puta dnevno", "nekoliko puta tje…
4 filter() za odabir redova po uvjetu
Funkcija filter() je dplyr glagol za odabir redova koji zadovoljavaju jedan ili više uvjeta. Rezultat je tibble koji sadrži samo retke za koje su svi uvjeti TRUE. Redovi za koje je uvjet FALSE ili NA se odbacuju.
4.1 Osnovni uvjeti
# Samo ispitanici iz Zagreba
raw |>
filter(grad == "Zagreb") |>
nrow()[1] 100
# Ispitanici mlađi od 21
raw |>
filter(dob < 21) |>
head(5)# A tibble: 5 × 17
id_respondenta timestamp dob spol grad godina_studija tv_min_dan
<dbl> <dttm> <dbl> <chr> <chr> <chr> <chr>
1 1 2025-03-28 17:05:00 20 žens… Zagr… 2 0
2 4 2025-03-28 12:54:00 18 fema… Split 1 <NA>
3 13 2025-03-25 15:16:00 20 muški Zagr… 1 0
4 14 2025-04-20 11:09:00 20 Žens… Zagr… 2 0
5 16 2025-04-07 18:06:00 19 muški Split 1 0
# ℹ 10 more variables: portali_min_dan <dbl>, drustvene_mreze_min_dan <dbl>,
# radio_min_dan <dbl>, podcast_min_dan <dbl>, povjerenje_tv_1_10 <dbl>,
# povjerenje_portali_1_10 <dbl>, povjerenje_drustvene_mreze_1_10 <dbl>,
# broj_platformi <dbl>, koje_platforme_koristi <chr>,
# koliko_cesto_prati_vijesti <chr>
Svaki poziv filter() zapravo evaluira logički izraz za svaki redak. Za prvi primjer, R prolazi kroz svaki od 250 redova i provjerava je li vrijednost u stupcu grad jednaka “Zagreb”. Retci za koje je odgovor TRUE ostaju, ostali nestaju.
4.2 Kombiniranje uvjeta
Snaga filter() dolazi do izražaja kad kombinirate više uvjeta. Unutar jednog filter() poziva, uvjeti odvojeni zarezom automatski se kombiniraju s I operatorom (&).
# Ispitanici iz Zagreba mlađi od 22
# Zarez između uvjeta je ekvivalentan &
raw |>
filter(grad == "Zagreb", dob < 22) |>
nrow()[1] 41
# Isto kao:
raw |>
filter(grad == "Zagreb" & dob < 22) |>
nrow()[1] 41
Oba pristupa daju identičan rezultat. Zarez je kraći za pisanje, & je eksplicitniji. Koristite što vam je čitljivije.
Za ILI uvjete, morate eksplicitno koristiti | operator.
# Ispitanici iz Zagreba ILI Splita
raw |>
filter(grad == "Zagreb" | grad == "Split") |>
count(grad)# A tibble: 2 × 2
grad n
<chr> <int>
1 Split 44
2 Zagreb 100
# Elegantnije s %in%
raw |>
filter(grad %in% c("Zagreb", "Split", "Rijeka")) |>
count(grad, sort = TRUE)# A tibble: 3 × 2
grad n
<chr> <int>
1 Zagreb 100
2 Split 44
3 Rijeka 18
Operator %in% smo upoznali prošli tjedan. U kontekstu filter() je izuzetno koristan jer zamjenjuje dugačke nizove ILI uvjeta jednim kompaktnim izrazom. Kad imate više od dvije kategorije, uvijek koristite %in%.
4.3 Filtriranje numeričkih raspona
# Ispitanici koji koriste društvene mreže između 60 i 180 minuta dnevno
raw |>
filter(drustvene_mreze_min_dan >= 60, drustvene_mreze_min_dan <= 180) |>
nrow()[1] 202
# Alternativa s between()
raw |>
filter(between(drustvene_mreze_min_dan, 60, 180)) |>
nrow()[1] 202
Funkcija between(x, left, right) je kratica za x >= left & x <= right. Oba pristupa daju isti rezultat, ali between() je čitljiviji kad filtrirate po rasponu.
4.4 Filtriranje teksta
Za tekstualne stupce, osim točnog podudaranja (==) i pripadnosti skupu (%in%), koristimo funkciju str_detect() iz paketa stringr (dio tidyverse) za pretraživanje po uzorku.
# Ispitanici čije platforme uključuju "Instagram" (bilo gdje u tekstu)
raw |>
filter(str_detect(koje_platforme_koristi, "Instagram")) |>
nrow()[1] 39
# Ispitanici koji prate vijesti barem jednom dnevno
raw |>
filter(str_detect(koliko_cesto_prati_vijesti, "dnevno")) |>
count(koliko_cesto_prati_vijesti)# A tibble: 2 × 2
koliko_cesto_prati_vijesti n
<chr> <int>
1 jednom dnevno 60
2 više puta dnevno 81
Funkcija str_detect() vraća TRUE ako tekstualni uzorak postoji bilo gdje u vrijednosti. Ovo je mnogo fleksibilnije od == jer ne zahtijeva točno podudaranje. Na primjer, str_detect(x, "dnevno") hvata i “više puta dnevno” i “jednom dnevno”.
4.5 filter() i nedostajuće vrijednosti
Važno svojstvo filter() je da automatski odbacuje retke s NA u uvjetu. Ovo je uglavnom poželjno ponašanje, ali morate biti svjesni da se događa.
# Koliko redova imamo ukupno?
nrow(raw)[1] 250
# Koliko ima NA u stupcu radio_min_dan?
sum(is.na(raw$radio_min_dan))[1] 32
# filter s numeričkim uvjetom na stupcu s NA
raw |>
filter(radio_min_dan > 0) |>
nrow()[1] 92
Rezultat ne uključuje retke s NA u stupcu radio_min_dan. Ako želite eksplicitno zadržati retke s NA, morate to navesti.
# Zadrži retke gdje je radio > 0 ILI je NA
raw |>
filter(radio_min_dan > 0 | is.na(radio_min_dan)) |>
nrow()[1] 124
# Zadrži SAMO retke s NA
raw |>
filter(is.na(radio_min_dan)) |>
nrow()[1] 32
# Izbaci retke s NA (zadrži samo kompletne)
raw |>
filter(!is.na(radio_min_dan)) |>
nrow()[1] 218
Kombinacija filter(!is.na(stupac)) je način da zadržite samo retke s poznatim vrijednostima u tom stupcu. Alternativno, funkcija drop_na() iz paketa tidyr uklanja retke koji imaju NA u bilo kojem stupcu (ili u specificiranim stupcima).
# Ukloni retke s NA u specifičnom stupcu
raw |>
drop_na(radio_min_dan) |>
nrow()[1] 218
# Ukloni retke s NA u BILO KOJEM stupcu (agresivno!)
raw |>
drop_na() |>
nrow()[1] 149
Primijetite drastičnu razliku. Kad koristimo drop_na() bez argumenata, gubimo mnogo redova jer se uklanjaju svi retci koji imaju NA u ijednom stupcu. U praksi, drop_na() bez argumenata se rijetko koristi jer je previše agresivan. Bolje je ciljano raditi s NA u stupcima koji vas zapravo zanimaju.
Svaki put kad koristite filter() ili drop_na(), dokumentirajte koliko redova ste izgubili i zašto. Ako ste od 250 ispitanika zadržali samo 150, to je informacija koju morate navesti u metodološkom dijelu rada. Čitatelj mora znati na koliko se opažanja vaši rezultati temelje i zašto su neka isključena.
5 select() za odabir i preimenovanje stupaca
Dok filter() radi s redovima, select() radi sa stupcima. Koristi se za tri svrhe, a to su odabir stupaca koji vam trebaju, uklanjanje stupaca koji vam ne trebaju i preimenovanje stupaca.
5.2 Uklanjanje stupaca
Minus ispred imena stupca znači “sve osim ovoga”.
# Sve osim timestampa i ID-a
raw |>
select(-timestamp, -id_respondenta) |>
names() [1] "dob" "spol"
[3] "grad" "godina_studija"
[5] "tv_min_dan" "portali_min_dan"
[7] "drustvene_mreze_min_dan" "radio_min_dan"
[9] "podcast_min_dan" "povjerenje_tv_1_10"
[11] "povjerenje_portali_1_10" "povjerenje_drustvene_mreze_1_10"
[13] "broj_platformi" "koje_platforme_koristi"
[15] "koliko_cesto_prati_vijesti"
# Uklanjanje raspona
raw |>
select(-(povjerenje_tv_1_10:povjerenje_drustvene_mreze_1_10)) |>
names() [1] "id_respondenta" "timestamp"
[3] "dob" "spol"
[5] "grad" "godina_studija"
[7] "tv_min_dan" "portali_min_dan"
[9] "drustvene_mreze_min_dan" "radio_min_dan"
[11] "podcast_min_dan" "broj_platformi"
[13] "koje_platforme_koristi" "koliko_cesto_prati_vijesti"
5.3 Pomoćne funkcije za odabir
Kad imate mnogo stupaca, ručno nabrajanje postaje nepraktično. dplyr nudi pomoćne funkcije za pametni odabir.
# Stupci čije ime počinje s "povjerenje"
raw |>
select(starts_with("povjerenje")) |>
head(3)# A tibble: 3 × 3
povjerenje_tv_1_10 povjerenje_portali_1_10 povjerenje_drustvene_mreze_1_10
<dbl> <dbl> <dbl>
1 2 6 4
2 3 5 3
3 4 6 1
# Stupci čije ime završava s "dan"
raw |>
select(ends_with("dan")) |>
head(3)# A tibble: 3 × 5
tv_min_dan portali_min_dan drustvene_mreze_min_dan radio_min_dan
<chr> <dbl> <dbl> <dbl>
1 0 40 59 49
2 0 20 101 NA
3 65 0 177 0
# ℹ 1 more variable: podcast_min_dan <dbl>
# Stupci čije ime sadrži "min"
raw |>
select(contains("min")) |>
head(3)# A tibble: 3 × 5
tv_min_dan portali_min_dan drustvene_mreze_min_dan radio_min_dan
<chr> <dbl> <dbl> <dbl>
1 0 40 59 49
2 0 20 101 NA
3 65 0 177 0
# ℹ 1 more variable: podcast_min_dan <dbl>
# Samo numerički stupci
raw |>
select(where(is.numeric)) |>
head(3)# A tibble: 3 × 10
id_respondenta dob portali_min_dan drustvene_mreze_min_dan radio_min_dan
<dbl> <dbl> <dbl> <dbl> <dbl>
1 1 20 40 59 49
2 2 27 20 101 NA
3 3 27 0 177 0
# ℹ 5 more variables: podcast_min_dan <dbl>, povjerenje_tv_1_10 <dbl>,
# povjerenje_portali_1_10 <dbl>, povjerenje_drustvene_mreze_1_10 <dbl>,
# broj_platformi <dbl>
Funkcija starts_with() bira stupce čije ime počinje zadanim tekstom. ends_with() bira po završetku. contains() traži tekst bilo gdje u imenu. where() prima funkciju za provjeru tipa i bira stupce koji zadovoljavaju taj uvjet. Ove funkcije postaju neprocjenjive kad radite s datasetima koji imaju 50 ili 100 stupaca (što nije neuobičajeno u anketnim istraživanjima).
5.4 Preimenovanje stupaca
Unutar select() možete preimenovati stupac sintaksom novo_ime = staro_ime. Ili koristite zasebnu funkciju rename() koja preimenu stupce ali zadrži sve ostale.
# Preimenovanje unutar select (odabire SAMO navedene stupce)
raw |>
select(
id = id_respondenta,
dob,
spol,
sm_minuta = drustvene_mreze_min_dan
) |>
head(3)# A tibble: 3 × 4
id dob spol sm_minuta
<dbl> <dbl> <chr> <dbl>
1 1 20 ženski 59
2 2 27 Muški 101
3 3 27 muški 177
# rename() mijenja imena ali zadržava sve stupce
raw |>
rename(
id = id_respondenta,
sm_minuta = drustvene_mreze_min_dan
) |>
names() [1] "id" "timestamp"
[3] "dob" "spol"
[5] "grad" "godina_studija"
[7] "tv_min_dan" "portali_min_dan"
[9] "sm_minuta" "radio_min_dan"
[11] "podcast_min_dan" "povjerenje_tv_1_10"
[13] "povjerenje_portali_1_10" "povjerenje_drustvene_mreze_1_10"
[15] "broj_platformi" "koje_platforme_koristi"
[17] "koliko_cesto_prati_vijesti"
Razlika je važna. select() s preimenovanjem zadržava samo stupce koje ste naveli. rename() zadržava sve stupce i samo mijenja imena onih koje ste specificirali. U praksi, rename() je sigurniji izbor kad želite samo promijeniti ime jednog ili dva stupca bez gubitka ostalih.
5.5 Preuređivanje stupaca
Funkcija relocate() premješta stupce na drugu poziciju u datasetu.
# Premjesti grad na početak (odmah nakon ID-a)
raw |>
relocate(grad, .after = id_respondenta) |>
head(3)# A tibble: 3 × 17
id_respondenta grad timestamp dob spol godina_studija tv_min_dan
<dbl> <chr> <dttm> <dbl> <chr> <chr> <chr>
1 1 Zagr… 2025-03-28 17:05:00 20 žens… 2 0
2 2 Zadar 2025-04-20 21:11:00 27 Muški 3 0
3 3 Zagr… 2025-04-18 14:48:00 27 muški 1 65
# ℹ 10 more variables: portali_min_dan <dbl>, drustvene_mreze_min_dan <dbl>,
# radio_min_dan <dbl>, podcast_min_dan <dbl>, povjerenje_tv_1_10 <dbl>,
# povjerenje_portali_1_10 <dbl>, povjerenje_drustvene_mreze_1_10 <dbl>,
# broj_platformi <dbl>, koje_platforme_koristi <chr>,
# koliko_cesto_prati_vijesti <chr>
# Premjesti sve numeričke stupce na kraj
raw |>
relocate(where(is.numeric), .after = last_col()) |>
head(3)# A tibble: 3 × 17
timestamp spol grad godina_studija tv_min_dan
<dttm> <chr> <chr> <chr> <chr>
1 2025-03-28 17:05:00 ženski Zagreb 2 0
2 2025-04-20 21:11:00 Muški Zadar 3 0
3 2025-04-18 14:48:00 muški Zagreb 1 65
# ℹ 12 more variables: koje_platforme_koristi <chr>,
# koliko_cesto_prati_vijesti <chr>, id_respondenta <dbl>, dob <dbl>,
# portali_min_dan <dbl>, drustvene_mreze_min_dan <dbl>, radio_min_dan <dbl>,
# podcast_min_dan <dbl>, povjerenje_tv_1_10 <dbl>,
# povjerenje_portali_1_10 <dbl>, povjerenje_drustvene_mreze_1_10 <dbl>,
# broj_platformi <dbl>
relocate() ne dodaje niti uklanja stupce, samo ih premješta. Ovo je korisno za organizaciju dataseta kad želite da relevantni stupci budu jedni do drugih.
6 mutate() za kreiranje i transformaciju varijabli
Funkcija mutate() je najsvestraniji dplyr glagol. Služi za kreiranje novih stupaca na temelju postojećih, transformaciju postojećih stupaca i rekodiranje vrijednosti. Rezultat je tibble s istim brojem redova ali potencijalno novim ili izmijenjenim stupcima.
6.1 Kreiranje novih varijabli
# Ukupno dnevno korištenje medija (portal + društvene mreže)
raw |>
mutate(
ukupno_digital = portali_min_dan + drustvene_mreze_min_dan
) |>
select(id_respondenta, portali_min_dan, drustvene_mreze_min_dan, ukupno_digital) |>
head(8)# A tibble: 8 × 4
id_respondenta portali_min_dan drustvene_mreze_min_dan ukupno_digital
<dbl> <dbl> <dbl> <dbl>
1 1 40 59 99
2 2 20 101 121
3 3 0 177 177
4 4 11 71 82
5 5 32 161 193
6 6 25 155 180
7 7 81 114 195
8 8 28 119 147
mutate() evaluira izraz na desnoj strani znaka jednakosti za svaki redak i rezultat pohranjuje u novi stupac nazvan imenom na lijevoj strani. Kao i kod vektoriziranih operacija, R automatski primjenjuje operaciju redak po redak.
Možete kreirati više stupaca u jednom mutate() pozivu, i kasniji stupci mogu koristiti ranije definirane.
raw |>
mutate(
ukupno_digital = portali_min_dan + drustvene_mreze_min_dan,
ukupno_sati = ukupno_digital / 60,
iznad_2_sata = ukupno_sati > 2
) |>
select(id_respondenta, ukupno_digital, ukupno_sati, iznad_2_sata) |>
head(8)# A tibble: 8 × 4
id_respondenta ukupno_digital ukupno_sati iznad_2_sata
<dbl> <dbl> <dbl> <lgl>
1 1 99 1.65 FALSE
2 2 121 2.02 TRUE
3 3 177 2.95 TRUE
4 4 82 1.37 FALSE
5 5 193 3.22 TRUE
6 6 180 3 TRUE
7 7 195 3.25 TRUE
8 8 147 2.45 TRUE
Primijetite da smo u istom mutate() pozivu najprije izračunali ukupno_digital, zatim ga koristili za izračun ukupno_sati, a onda ukupno_sati za logički stupac iznad_2_sata. Ova mogućnost referiranja na upravo kreirane stupce čini mutate() izuzetno moćnim.
6.2 Transformacija postojećih stupaca
mutate() može i prepisati postojeći stupac.
# Zaokruži portal minute na desetice (prepisuje stupac)
raw |>
mutate(
portali_min_dan = round(portali_min_dan, -1)
) |>
select(id_respondenta, portali_min_dan) |>
head(8)# A tibble: 8 × 2
id_respondenta portali_min_dan
<dbl> <dbl>
1 1 40
2 2 20
3 3 0
4 4 10
5 5 30
6 6 20
7 7 80
8 8 30
Kad date mutate stupcu isto ime kao postojeći stupac, novi vrijednosti zamjenjuju stare. Ovo je korisno za čišćenje podataka (na primjer, pretvorbu teksta u mala slova), ali budite oprezni jer originalne vrijednosti nestaju. Dobra praksa je raditi transformacije na kopiji dataseta, ne na originalu.
6.3 Čišćenje stupca spol pomoću str_to_lower() i case_when()
Pogledajmo koliko je neuredan stupac spol u našim podacima.
raw |>
count(spol, sort = TRUE)# A tibble: 12 × 2
spol n
<chr> <int>
1 Muški 48
2 Ženski 45
3 muški 42
4 ženski 31
5 Ž 16
6 male 14
7 M 11
8 Musko 11
9 ž 10
10 m 9
11 Zensko 7
12 female 6
Imamo dvanaestak varijanti istih dviju kategorija. “Ženski”, “ženski”, “Ž”, “ž”, “Zensko”, “female” bi sve trebalo biti jedna kategorija. Ovo je klasičan problem u anketnim podacima i jedan od najčešćih razloga za čišćenje.
Funkcija case_when() je najfleksibilniji alat za rekodiranje. Radi kao niz IF-THEN pravila. Za svaki redak, R provjerava uvjete redom i dodjeljuje vrijednost prvog uvjeta koji je ispunjen.
raw <- raw |>
mutate(
spol_clean = case_when(
str_to_lower(spol) %in% c("ženski", "ž", "zensko", "female") ~ "ženski",
str_to_lower(spol) %in% c("muški", "m", "musko", "male") ~ "muški",
.default = "ostalo"
)
)
raw |>
count(spol_clean)# A tibble: 2 × 2
spol_clean n
<chr> <int>
1 muški 135
2 ženski 115
Raščlanimo ovaj kod. Funkcija str_to_lower() pretvara tekst u mala slova, čime eliminiramo razliku između “Ženski” i “ženski”. Zatim %in% provjerava pripada li vrijednost jednom od navedenih oblika. Ako da, dodjeljuje standardizirani oblik. Argument .default hvata sve što ne odgovara nijednom uvjetu.
Rezultat je čist stupac spol_clean s tri konzistentne kategorije umjesto dvanaest neujednačenih varijanti.
Kad čistite tekstualne podatke, uvijek najprije pretvorite u mala slova pomoću str_to_lower(). Ovo odmah eliminira najčešći izvor nekonzistentnosti (razliku u kapitalizaciji) i smanjuje broj slučajeva koje morate pokriti u case_when(). Redoslijed je važan. Najprije trebate koristiti str_to_lower(), pa tek onda provjerite uvjete.
6.4 Rekodiranje numeričkih varijabli u kategorije
Čest zadatak u komunikologiji je pretvaranje kontinuirane varijable u kategorije. Na primjer, umjesto točne dobi, želimo dobne skupine.
raw <- raw |>
mutate(
dobna_skupina = case_when(
dob < 20 ~ "18-19",
dob < 22 ~ "20-21",
dob < 24 ~ "22-23",
dob >= 24 ~ "24+"
)
)
raw |>
count(dobna_skupina)# A tibble: 4 × 2
dobna_skupina n
<chr> <int>
1 18-19 73
2 20-21 61
3 22-23 47
4 24+ 69
Redoslijed uvjeta u case_when() je bitan. R provjerava uvjete odozgo prema dolje i dodjeljuje vrijednost prvog ispunjenog uvjeta. Ako osoba ima 19 godina, prvi uvjet (dob < 20) je TRUE i dodjeljuje se “18-19”. R ne provjerava preostale uvjete. Zato uvjete postavljamo od najspecifičnijeg prema najopćenitijem.
6.5 if_else(): binarno rekodiranje
Za jednostavne da/ne situacije, if_else() je kraći od case_when().
raw <- raw |>
mutate(
visoko_koristenje_sm = if_else(drustvene_mreze_min_dan > 120, "visoko", "nisko/umjereno"),
prati_vijesti_cesto = if_else(
koliko_cesto_prati_vijesti %in% c("više puta dnevno", "jednom dnevno"),
TRUE,
FALSE
)
)
raw |>
count(visoko_koristenje_sm)# A tibble: 2 × 2
visoko_koristenje_sm n
<chr> <int>
1 nisko/umjereno 117
2 visoko 133
Funkcija if_else() prima tri argumenta, uključujući uvjet, vrijednost za TRUE i vrijednost za FALSE. Prednost nad base R ifelse() je što if_else() strogo provjerava tipove i daje razumljivije greške kad nešto ne štima.
6.6 Čišćenje stupca godina studija
Pogledajmo još jedan neuredan stupac.
raw |>
count(godina_studija, sort = TRUE)# A tibble: 11 × 2
godina_studija n
<chr> <int>
1 2 54
2 1 52
3 3 38
4 3. 22
5 1. 19
6 4 19
7 druga 14
8 5 12
9 2. 7
10 treća 7
11 prva 6
Imamo “1”, “1.”, “prva”, “2”, “2.”, “druga” i tako dalje. Sve to treba svesti na konzistentne brojeve.
raw <- raw |>
mutate(
godina_clean = case_when(
str_to_lower(godina_studija) %in% c("1", "1.", "prva") ~ 1,
str_to_lower(godina_studija) %in% c("2", "2.", "druga") ~ 2,
str_to_lower(godina_studija) %in% c("3", "3.", "treća", "treca") ~ 3,
str_to_lower(godina_studija) %in% c("4", "4.", "četvrta", "cetvrta") ~ 4,
str_to_lower(godina_studija) %in% c("5", "5.", "peta") ~ 5,
.default = NA_real_
)
)
raw |>
count(godina_clean)# A tibble: 5 × 2
godina_clean n
<dbl> <int>
1 1 77
2 2 75
3 3 67
4 4 19
5 5 12
Ovaj put smo neprepoznate vrijednosti kodirali kao NA_real_ (NA numeričkog tipa) umjesto tekstualne kategorije. To je ispravniji pristup kad očekujemo numerički rezultat. Ako neka vrijednost ne odgovara nijednom poznatom obrascu, bolje je eksplicitno reći “ne znam” (NA) nego nagađati.
6.7 Rad s problematičnim numeričkim stupcima
Prisjetimo se da je stupac tv_min_dan učitan kao tekst jer sadrži i brojeve i tekst (“ne gledam”) i prazne ćelije. Moramo ga pretvoriti u broj.
# Pogledajmo problematične vrijednosti
raw |>
count(tv_min_dan, sort = TRUE) |>
head(15)# A tibble: 15 × 2
tv_min_dan n
<chr> <int>
1 0 85
2 <NA> 41
3 ne gledam 6
4 71 4
5 10 3
6 112 3
7 119 3
8 26 3
9 48 3
10 49 3
11 51 3
12 7 3
13 76 3
14 82 3
15 104 2
# "ne gledam" tretiramo kao 0, prazne kao NA
raw <- raw |>
mutate(
tv_minuta = case_when(
tv_min_dan == "ne gledam" ~ 0,
tv_min_dan == "" ~ NA_real_,
.default = as.numeric(tv_min_dan)
)
)
# Provjera
raw |>
select(tv_min_dan, tv_minuta) |>
filter(is.na(tv_minuta) | tv_minuta == 0) |>
head(10)# A tibble: 10 × 2
tv_min_dan tv_minuta
<chr> <dbl>
1 0 0
2 0 0
3 <NA> NA
4 <NA> NA
5 0 0
6 <NA> NA
7 0 0
8 0 0
9 0 0
10 <NA> NA
Sada imamo čist numerički stupac tv_minuta u kojem je “ne gledam” pretvoreno u 0 (jer osoba zaista ne gleda TV, dakle 0 minuta), a prazne ćelije su NA (jer ne znamo koliko ta osoba gleda TV).
Ova razlika između 0 i NA je konceptualno važna i vraća nas na tipove nedostajućih vrijednosti koje smo spominjali u tjednu 2. Nula znači “znamo odgovor, i odgovor je ništa”. NA znači “ne znamo odgovor”.
7 arrange(): sortiranje podataka
Funkcija arrange() sortira retke po vrijednostima jednog ili više stupaca. Po defaultu sortira uzlazno (od najmanjeg prema najvećem ili abecedno). Za silazno sortiranje koristimo desc().
# Sortirano po dobi (najmlađi prvi)
raw |>
select(id_respondenta, dob, grad, drustvene_mreze_min_dan) |>
arrange(dob) |>
head(8)# A tibble: 8 × 4
id_respondenta dob grad drustvene_mreze_min_dan
<dbl> <dbl> <chr> <dbl>
1 4 18 Split 71
2 23 18 Split 145
3 25 18 Osijek 184
4 31 18 Zagreb 97
5 39 18 Šibenik 154
6 44 18 Zagreb 152
7 45 18 Pula 86
8 57 18 Zagreb 192
# Sortirano po korištenju društvenih mreža (najviše korištenja prvo)
raw |>
select(id_respondenta, dob, grad, drustvene_mreze_min_dan) |>
arrange(desc(drustvene_mreze_min_dan)) |>
head(8)# A tibble: 8 × 4
id_respondenta dob grad drustvene_mreze_min_dan
<dbl> <dbl> <chr> <dbl>
1 149 22 Zadar 296
2 21 21 Slavonski Brod 271
3 42 25 Zagreb 246
4 87 18 Zagreb 246
5 141 23 Split 222
6 181 22 Split 217
7 16 19 Split 214
8 79 22 Rijeka 206
7.1 Sortiranje po više stupaca
Kad sortirate po više stupaca, prvi stupac ima prioritet. Unutar istih vrijednosti prvog stupca, koristi se drugi za razrješenje.
# Sortiraj po gradu (abecedno), unutar grada po dobi (silazno)
raw |>
select(id_respondenta, grad, dob, drustvene_mreze_min_dan) |>
arrange(grad, desc(dob)) |>
head(12)# A tibble: 12 × 4
id_respondenta grad dob drustvene_mreze_min_dan
<dbl> <chr> <dbl> <dbl>
1 241 Dubrovnik 27 165
2 198 Dubrovnik 25 99
3 34 Dubrovnik 23 151
4 222 Dubrovnik 21 109
5 243 Dubrovnik 20 117
6 129 Dubrovnik 19 203
7 75 Karlovac 28 115
8 8 Karlovac 26 119
9 229 Karlovac 24 134
10 15 Karlovac 23 35
11 73 Karlovac 21 20
12 95 Karlovac 20 147
Vidimo da su najprije svi ispitanici iz Dubrovnika (abecedno prvi), unutar kojih je najstariji na vrhu. Zatim dolazi Karlovac, pa dalje.
7.2 Gdje se NA pojavljuje pri sortiranju?
# NA vrijednosti uvijek idu na kraj, bez obzira na smjer
raw |>
select(id_respondenta, radio_min_dan) |>
arrange(radio_min_dan) |>
tail(8)# A tibble: 8 × 2
id_respondenta radio_min_dan
<dbl> <dbl>
1 207 NA
2 211 NA
3 217 NA
4 218 NA
5 219 NA
6 220 NA
7 233 NA
8 244 NA
R stavlja NA na kraj sortiranog dataseta, neovisno o tome sortirate li uzlazno ili silazno. Ovo je korisno znati jer ćete ponekad htjeti vidjeti retke s nedostajućim vrijednostima, a oni su uvijek na dnu.
8 Kombiniranje glagola u pipeline
Prava snaga dplyr-a nije u pojedinačnim glagolima nego u njihovoj kombinaciji. Pipe operator (|>) omogućuje ulančavanje operacija u jednu koherentnu sekvencu koja čita dataseta od početka do kraja, korak po korak.
Pogledajmo realistični primjer. Želimo odgovoriti na pitanje te saznati koji gradovi imaju studente koji najviše koriste društvene mreže.
raw |>
filter(dob >= 18, dob <= 25) |>
select(grad, drustvene_mreze_min_dan) |>
group_by(grad) |>
summarise(
n = n(),
prosjek = round(mean(drustvene_mreze_min_dan), 1),
.groups = "drop"
) |>
filter(n >= 5) |>
arrange(desc(prosjek))# A tibble: 11 × 3
grad n prosjek
<chr> <int> <dbl>
1 Slavonski Brod 8 141.
2 Zadar 11 137.
3 Dubrovnik 5 136.
4 Pula 9 134.
5 Split 40 133.
6 Šibenik 9 127.
7 Osijek 18 124.
8 Zagreb 79 116.
9 Rijeka 18 112.
10 Varaždin 5 79.6
11 Karlovac 6 79.3
Čitamo odozgo prema dolje. Uzmi sirove podatke. Zadrži samo ispitanike između 18 i 25 godina. Odaberi samo stupce za grad i minute korištenja. Grupiraj po gradu. Izračunaj broj ispitanika i prosječno korištenje za svaki grad. Zadrži samo gradove s barem 5 ispitanika (da prosjeci budu smisleni). Sortiraj po prosječnom korištenju od najvišeg prema najnižem.
Svaki korak je sam za sebe jasan, a zajedno tvore kompletnu analizu. Ovo je radni obrazac koji ćete koristiti stotine puta.
Pogledajmo drugi primjer. Trebamo profil tipičnog korisnika koji često prati vijesti.
raw |>
filter(prati_vijesti_cesto == TRUE) |>
summarise(
n = n(),
prosjek_dob = round(mean(dob), 1),
prosjek_sm_min = round(mean(drustvene_mreze_min_dan), 1),
prosjek_portal_min = round(mean(portali_min_dan), 1),
prosjek_trust_portal = round(mean(povjerenje_portali_1_10), 1),
prosjek_trust_sm = round(mean(povjerenje_drustvene_mreze_1_10), 1)
)# A tibble: 1 × 6
n prosjek_dob prosjek_sm_min prosjek_portal_min prosjek_trust_portal
<int> <dbl> <dbl> <dbl> <dbl>
1 141 21.9 122. 44.2 4.9
# ℹ 1 more variable: prosjek_trust_sm <dbl>
Ljudi koji prate vijesti barem jednom dnevno provode određen broj minuta na portalima i društvenim mrežama te imaju specifičan profil povjerenja u različite medije. Ova tablica daje bogat uvid u jednom pipeline.
8.1 Pipeline za čišćenje podataka
Uobičajena praksa je napisati jedan veliki pipeline za čišćenje koji pretvara sirove podatke u analizi spreman dataset. Evo kako bi to izgledalo za naše podatke.
clean <- raw |>
# Preimenovanje stupaca za čitljivost
rename(
id = id_respondenta,
sm_min = drustvene_mreze_min_dan,
portal_min = portali_min_dan,
trust_tv = povjerenje_tv_1_10,
trust_portal = povjerenje_portali_1_10,
trust_sm = povjerenje_drustvene_mreze_1_10,
n_platformi = broj_platformi,
vijesti_frekvencija = koliko_cesto_prati_vijesti
) |>
# Korištenje već očišćenih stupaca
select(
id, dob, spol_clean, grad, godina_clean,
tv_minuta, portal_min, sm_min, radio_min_dan, podcast_min_dan,
trust_tv, trust_portal, trust_sm,
n_platformi, vijesti_frekvencija,
dobna_skupina, visoko_koristenje_sm, prati_vijesti_cesto
) |>
# Završno preimenovanje čistih stupaca
rename(
spol = spol_clean,
godina = godina_clean,
radio_min = radio_min_dan,
podcast_min = podcast_min_dan
)
glimpse(clean)Rows: 250
Columns: 18
$ id <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15…
$ dob <dbl> 20, 27, 27, 18, 25, 26, 28, 26, 22, 21, 22, 27, 2…
$ spol <chr> "ženski", "muški", "muški", "ženski", "ženski", "…
$ grad <chr> "Zagreb", "Zadar", "Zagreb", "Split", "Zagreb", "…
$ godina <dbl> 2, 3, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 1, 2, 2, 1, 1…
$ tv_minuta <dbl> 0, 0, 65, NA, NA, 91, 91, 0, 76, 66, NA, 109, 0, …
$ portal_min <dbl> 40, 20, 0, 11, 32, 25, 81, 28, 37, 5, 38, 44, 26,…
$ sm_min <dbl> 59, 101, 177, 71, 161, 155, 114, 119, 56, 40, 129…
$ radio_min <dbl> 49, NA, 0, NA, 26, NA, 0, 0, 17, 0, 0, 13, 0, 0, …
$ podcast_min <dbl> 89, 0, 49, 0, 0, NA, NA, 31, 0, 19, 0, 29, 22, 25…
$ trust_tv <dbl> 2, 3, 4, 5, 5, 4, 3, 6, 6, 7, 2, 7, 7, 5, 4, 2, 5…
$ trust_portal <dbl> 6, 5, 6, 6, 3, 7, 7, 1, 7, 5, 6, 5, 5, 5, 4, 6, 4…
$ trust_sm <dbl> 4, 3, 1, 4, 4, 7, 2, 3, 4, 6, 1, 2, 3, 5, 2, 2, 4…
$ n_platformi <dbl> 9, 5, 7, 6, 5, 2, 1, 8, 5, 7, 6, 6, 2, 7, 4, 3, 6…
$ vijesti_frekvencija <chr> "više puta dnevno", "nekoliko puta tjedno", "više…
$ dobna_skupina <chr> "20-21", "24+", "24+", "18-19", "24+", "24+", "24…
$ visoko_koristenje_sm <chr> "nisko/umjereno", "nisko/umjereno", "visoko", "ni…
$ prati_vijesti_cesto <lgl> TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, TRU…
Sada imamo čist dataset clean s razumljivim imenima stupaca, konzistentnim kodiranjem spola i godine studija, numeričkim stupcem za TV minute i binarnim varijablama za visoko korištenje i praćenje vijesti. Ovaj dataset je spreman za deskriptivnu statistiku i vizualizaciju.
U svakom projektu analize podataka, trebali biste imati jasnu granicu između sirovih podataka (koje nikad ne mijenjate) i čistih podataka (koje kreirate skriptom iz sirovih). Skripta za čišćenje je vaš zapis svakog koraka, i svaki korišten uvjet mora biti dokumentiran komentarima.
9 Brzi pregled očišćenog dataseta
Provjerimo da je čišćenje uspjelo i iskoristimo priliku da povežemo sve naučene glagole.
# Distribucija po spolu
clean |>
count(spol)# A tibble: 2 × 2
spol n
<chr> <int>
1 muški 135
2 ženski 115
# Distribucija po gradu (top 5)
clean |>
count(grad, sort = TRUE) |>
head(5)# A tibble: 5 × 2
grad n
<chr> <int>
1 Zagreb 100
2 Split 44
3 Osijek 23
4 Rijeka 18
5 Zadar 15
# Prosječno korištenje medija po dobnim skupinama
clean |>
group_by(dobna_skupina) |>
summarise(
n = n(),
sm_prosjek = round(mean(sm_min), 1),
portal_prosjek = round(mean(portal_min), 1),
tv_prosjek = round(mean(tv_minuta, na.rm = TRUE), 1),
.groups = "drop"
)# A tibble: 4 × 5
dobna_skupina n sm_prosjek portal_prosjek tv_prosjek
<chr> <int> <dbl> <dbl> <dbl>
1 18-19 73 122 42.3 33
2 20-21 61 120 43.6 34.4
3 22-23 47 120. 42.9 35.6
4 24+ 69 122. 42.9 36.1
Tablica pokazuje jasne razlike. Studenti različitih dobnih skupina imaju različite obrasce korištenja medija. Najmlađi (18 do 19) provode najviše vremena na društvenim mrežama, dok je korištenje portala ravnomjernije raspoređeno. TV je konzistentno najniži oblik medijske konzumacije u svim skupinama, što je očekivano za studentsku populaciju.
# Povjerenje u medije — tko kome vjeruje
clean |>
summarise(
trust_tv_prosjek = round(mean(trust_tv), 1),
trust_portal_prosjek = round(mean(trust_portal), 1),
trust_sm_prosjek = round(mean(trust_sm), 1)
)# A tibble: 1 × 3
trust_tv_prosjek trust_portal_prosjek trust_sm_prosjek
<dbl> <dbl> <dbl>
1 4.5 5 3.4
# Povjerenje po spolu
clean |>
group_by(spol) |>
summarise(
n = n(),
trust_sm = round(mean(trust_sm), 1),
trust_portal = round(mean(trust_portal), 1),
.groups = "drop"
)# A tibble: 2 × 4
spol n trust_sm trust_portal
<chr> <int> <dbl> <dbl>
1 muški 135 3.3 4.9
2 ženski 115 3.5 5.1
Studenti u prosjeku najviše vjeruju portalima, zatim televiziji, a najmanje društvenim mrežama. Ovo je zanimljiv nalaz jer istovremeno na društvenim mrežama provode daleko najviše vremena. Provode li ljudi najviše vremena na medijima kojima najmanje vjeruju? Ili se povjerenje gradi korištenjem? Ovo su pitanja na koja ćemo se vraćati kad budemo radili korelacije i regresiju u kasnijim tjednima.
10 group_by() i summarise() za statistike po grupama
Kombinaciju group_by() i summarise() smo već koristili u dosadašnjim primjerima, ali zaslužuje detaljniju obradu jer je ovo daleko najvažniji obrazac u cijelom tidyverse radnom toku. Gotovo svaka analiza u komunikologiji uključuje usporedbu između grupa: razlikuju li se muškarci i žene po korištenju medija? Razlikuju li se gradovi po povjerenju? Razlikuju li se generacije po izvorima vijesti?
10.1 Osnovna logika
group_by() dijeli tibble na nevidljive podskupove prema vrijednostima jednog ili više stupaca. Sam po sebi ne proizvodi nikakav vidljiv rezultat. Ali kad nakon njega pozovete summarise(), izračun se ponavlja zasebno za svaki podskup.
# Prosječno korištenje društvenih mreža po spolu
clean |>
group_by(spol) |>
summarise(
n = n(),
prosjek_sm = round(mean(sm_min), 1),
sd_sm = round(sd(sm_min), 1),
medijan_sm = median(sm_min),
.groups = "drop"
)# A tibble: 2 × 5
spol n prosjek_sm sd_sm medijan_sm
<chr> <int> <dbl> <dbl> <dbl>
1 muški 135 117. 49.2 115
2 ženski 115 126. 47.3 129
Argument .groups = "drop" na kraju govori R-u da ukloni grupiranje nakon izračuna. Bez njega, rezultirajući tibble bi ostao grupiran, što može uzrokovati neočekivano ponašanje u kasnijim operacijama. Dobra praksa je uvijek eksplicitno navesti .groups = "drop".
10.2 Grupiranje po više varijabli
# Korištenje po spolu i dobnoj skupini
clean |>
group_by(spol, dobna_skupina) |>
summarise(
n = n(),
prosjek_sm = round(mean(sm_min), 1),
prosjek_portal = round(mean(portal_min), 1),
.groups = "drop"
) |>
filter(spol != "ostalo") |>
arrange(dobna_skupina, spol)# A tibble: 8 × 5
spol dobna_skupina n prosjek_sm prosjek_portal
<chr> <chr> <int> <dbl> <dbl>
1 muški 18-19 39 122. 40.2
2 ženski 18-19 34 122. 44.7
3 muški 20-21 36 114. 41.4
4 ženski 20-21 25 128. 46.8
5 muški 22-23 24 110. 44.1
6 ženski 22-23 23 130. 41.7
7 muški 24+ 36 119. 42.9
8 ženski 24+ 33 126. 43
Kad grupirate po više varijabli, summarise() izračunava statistike za svaku kombinaciju tih varijabli. S dva spola i četiri dobne skupine dobivate osam grupa (ili manje, ako neke kombinacije nemaju opažanja). Filtrirali smo kategoriju “ostalo” jer s malim brojem opažanja statistike nisu pouzdane.
10.3 count() kao kratica
Funkcija count() je zapravo kratica za group_by() |> summarise(n = n()) |> ungroup(). Koristite je kad vam treba samo prebrojavanje.
# Ovo:
clean |>
count(grad, sort = TRUE) |>
head(5)# A tibble: 5 × 2
grad n
<chr> <int>
1 Zagreb 100
2 Split 44
3 Osijek 23
4 Rijeka 18
5 Zadar 15
# Je ekvivalentno ovome:
clean |>
group_by(grad) |>
summarise(n = n(), .groups = "drop") |>
arrange(desc(n)) |>
head(5)# A tibble: 5 × 2
grad n
<chr> <int>
1 Zagreb 100
2 Split 44
3 Osijek 23
4 Rijeka 18
5 Zadar 15
Obje verzije daju identičan rezultat, ali count() štedi tri reda koda. Za jednostavno prebrojavanje uvijek koristite count().
10.4 group_by() s mutate()
Manje poznata ali izuzetno korisna kombinacija je group_by() s mutate(). Umjesto da sažima podatke u jednu vrijednost po grupi (kao summarise()), mutate() dodaje novu kolonu svakom retku, ali izračun se radi unutar grupe.
# Z-score korištenja društvenih mreža UNUTAR svake dobne skupine
clean |>
group_by(dobna_skupina) |>
mutate(
sm_prosjek_grupe = mean(sm_min),
sm_z = round((sm_min - mean(sm_min)) / sd(sm_min), 2)
) |>
ungroup() |>
select(id, dob, dobna_skupina, sm_min, sm_prosjek_grupe, sm_z) |>
head(10)# A tibble: 10 × 6
id dob dobna_skupina sm_min sm_prosjek_grupe sm_z
<dbl> <dbl> <chr> <dbl> <dbl> <dbl>
1 1 20 20-21 59 120. -1.24
2 2 27 24+ 101 122. -0.53
3 3 27 24+ 177 122. 1.33
4 4 18 18-19 71 122. -0.98
5 5 25 24+ 161 122. 0.94
6 6 26 24+ 155 122. 0.79
7 7 28 24+ 114 122. -0.21
8 8 26 24+ 119 122. -0.09
9 9 22 22-23 56 120. -1.2
10 10 21 20-21 40 120. -1.62
Primijetite ungroup() na kraju. Kad koristite group_by() s mutate(), grupiranje ostaje aktivno nakon mutatea (za razliku od summarise() koji ga automatski smanjuje). Uvijek dodajte ungroup() kad završite s grupiranim operacijama da izbjegnete iznenađenja.
11 across() za istu operaciju na više stupaca
Do sada smo u summarise() ručno pisali svaku statistiku za svaki stupac. Kad imate pet ili deset numeričkih stupaca, to postaje zamorno. Funkcija across() rješava ovaj problem jer primjenjuje istu funkciju (ili više funkcija) na više stupaca odjednom.
# Prosjek za sve stupce koji sadrže "trust" u imenu
clean |>
summarise(
across(starts_with("trust"), ~round(mean(.x), 1))
)# A tibble: 1 × 3
trust_tv trust_portal trust_sm
<dbl> <dbl> <dbl>
1 4.5 5 3.4
Sintaksa ~round(mean(.x), 1) koristi lambda notaciju (tilda formula). .x je placeholder za svaki stupac na koji se across() primjenjuje. Ovo se čita kao “za svaki stupac koji počinje s trust, izračunaj zaokruženi prosjek”.
11.1 Više funkcija odjednom
# Prosjek i SD za stupce s minutama
clean |>
summarise(
across(
c(sm_min, portal_min, tv_minuta),
list(
prosjek = ~round(mean(.x, na.rm = TRUE), 1),
sd = ~round(sd(.x, na.rm = TRUE), 1)
),
.names = "{.col}_{.fn}"
)
)# A tibble: 1 × 6
sm_min_prosjek sm_min_sd portal_min_prosjek portal_min_sd tv_minuta_prosjek
<dbl> <dbl> <dbl> <dbl> <dbl>
1 121. 48.5 42.9 22.3 34.7
# ℹ 1 more variable: tv_minuta_sd <dbl>
Kad proslijedite imenovu listu funkcija, across() kreira zasebne stupce za svaku kombinaciju stupca i funkcije. Argument .names = "{.col}_{.fn}" kontrolira kako se novi stupci imenuju — {.col} je ime izvornog stupca, {.fn} je ime funkcije iz liste.
11.2 across() s group_by()
Kombinacija across() i group_by() omogućuje izračun više statistika za više stupaca po grupama, u jednom kompaktnom pozivu.
clean |>
group_by(dobna_skupina) |>
summarise(
n = n(),
across(
c(sm_min, portal_min, trust_sm, trust_portal),
list(M = ~round(mean(.x, na.rm = TRUE), 1)),
.names = "{.col}_{.fn}"
),
.groups = "drop"
)# A tibble: 4 × 6
dobna_skupina n sm_min_M portal_min_M trust_sm_M trust_portal_M
<chr> <int> <dbl> <dbl> <dbl> <dbl>
1 18-19 73 122 42.3 3.5 4.9
2 20-21 61 120 43.6 3.4 4.9
3 22-23 47 120. 42.9 3.5 5
4 24+ 69 122. 42.9 3.3 5.2
U jednom pozivu dobivamo prosjeke četiri varijable za svaku dobnu skupinu, plus broj opažanja. Ovo je obrazac koji ćete koristiti za izradu tablica deskriptivnih statistika u akademskim radovima.
11.3 across() s mutate()
across() radi i unutar mutate() za transformaciju više stupaca odjednom.
# Centriranje svih trust varijabli (oduzimanje prosjeka)
clean |>
mutate(
across(
starts_with("trust"),
~.x - mean(.x),
.names = "{.col}_cent"
)
) |>
select(id, starts_with("trust")) |>
head(5)# A tibble: 5 × 7
id trust_tv trust_portal trust_sm trust_tv_cent trust_portal_cent
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 2 6 4 -2.48 0.992
2 2 3 5 3 -1.48 -0.00800
3 3 4 6 1 -0.476 0.992
4 4 5 6 4 0.524 0.992
5 5 5 3 4 0.524 -2.01
# ℹ 1 more variable: trust_sm_cent <dbl>
Ovo je osobito korisno za standardizaciju ili transformaciju velikog broja varijabli u jednom koraku.
Funkcija across() čini kod kompaktnijim ali i teže čitljivim za početnike. Ako vam lambda notacija (~mean(.x)) izgleda zbunjujuće, nema ništa loše u tome da najprije pišete svaku statistiku ručno, a across() počnete koristiti kad se osjećate ugodno s osnovnim glagolima. Cilj je čitljivost, ne kratkoća.
12 pivot_longer() i pivot_wider() za preoblikovanje podataka
Ponekad podaci dolaze u obliku koji nije pogodan za analizu ili vizualizaciju i moramo ih preoblikovati. Dva najčešća slučaja su pretvaranje širokog formata u dugački i obrnuto.
12.1 Tidy data — princip urednih podataka
Wickham (2014) definira uredne podatke (tidy data) kao tablicu u kojoj svaki redak predstavlja jedno opažanje, svaki stupac jednu varijablu i svaka ćelija jednu vrijednost. Zvuči jednostavno, ali mnogi dataseti ne zadovoljavaju ovaj princip.
Pogledajmo konkretan primjer. Naši podaci o povjerenju imaju tri zasebna stupca: trust_tv, trust_portal, trust_sm. Za neke analize (posebno vizualizaciju), bilo bi korisnije imati jedan stupac medij s vrijednostima “TV”, “portal” i “društvene mreže” i jedan stupac povjerenje s numeričkom ocjenom.
12.2 pivot_longer() za pretvaranje od širokog prema dugačkom formatu
trust_long <- clean |>
select(id, dob, spol, dobna_skupina, trust_tv, trust_portal, trust_sm) |>
pivot_longer(
cols = starts_with("trust"),
names_to = "medij",
values_to = "povjerenje",
names_prefix = "trust_"
)
trust_long |>
head(12)# A tibble: 12 × 6
id dob spol dobna_skupina medij povjerenje
<dbl> <dbl> <chr> <chr> <chr> <dbl>
1 1 20 ženski 20-21 tv 2
2 1 20 ženski 20-21 portal 6
3 1 20 ženski 20-21 sm 4
4 2 27 muški 24+ tv 3
5 2 27 muški 24+ portal 5
6 2 27 muški 24+ sm 3
7 3 27 muški 24+ tv 4
8 3 27 muški 24+ portal 6
9 3 27 muški 24+ sm 1
10 4 18 ženski 18-19 tv 5
11 4 18 ženski 18-19 portal 6
12 4 18 ženski 18-19 sm 4
Funkcija pivot_longer() pretvara stupce u redove. Ključni argumenti funkcije su sljedeći.
cols specificira koje stupce pretvaramo (ovdje sve koji počinju s “trust”).
names_to je ime novog stupca koji će sadržavati imena izvornih stupaca.
values_to je ime novog stupca koji će sadržavati vrijednosti iz izvornih stupaca.
names_prefix uklanja zajednički prefiks iz imena (bez njega bismo imali “trust_tv” umjesto “tv”).
Iz originalnih 250 redova (jedan po ispitaniku) dobili smo 750 redova (tri po ispitaniku, jedan za svaki tip medija). Ovo je dugački format.
Sad možemo lako izračunati prosječno povjerenje po tipu medija.
trust_long |>
group_by(medij) |>
summarise(
prosjek = round(mean(povjerenje), 2),
sd = round(sd(povjerenje), 2),
.groups = "drop"
)# A tibble: 3 × 3
medij prosjek sd
<chr> <dbl> <dbl>
1 portal 5.01 1.73
2 sm 3.41 1.72
3 tv 4.48 1.99
Ili po tipu medija i dobnoj skupini.
trust_long |>
group_by(dobna_skupina, medij) |>
summarise(
prosjek = round(mean(povjerenje), 1),
.groups = "drop"
) |>
arrange(dobna_skupina, medij)# A tibble: 12 × 3
dobna_skupina medij prosjek
<chr> <chr> <dbl>
1 18-19 portal 4.9
2 18-19 sm 3.5
3 18-19 tv 4.6
4 20-21 portal 4.9
5 20-21 sm 3.4
6 20-21 tv 4.8
7 22-23 portal 5
8 22-23 sm 3.5
9 22-23 tv 4.1
10 24+ portal 5.2
11 24+ sm 3.3
12 24+ tv 4.4
Ova tablica jasno pokazuje obrasce koje bi bilo teško vidjeti u širokom formatu. Dugački format je posebno koristan za vizualizaciju jer ggplot2 (koji ćemo učiti sljedeći tjedan) radi prirodno s dugačkim podacima.
12.3 pivot_wider() za pretvaranje od dugačkog prema širokom formatu
Obrnuta operacija, pivot_wider(), pretvara redove u stupce. Korisna je kad želite tablicu u obliku koji je čitljiv za ljude (široki format), a ne za računalo (dugački format).
# Prosječno povjerenje po dobnoj skupini i mediju, u širokom formatu
trust_long |>
group_by(dobna_skupina, medij) |>
summarise(prosjek = round(mean(povjerenje), 1), .groups = "drop") |>
pivot_wider(
names_from = medij,
values_from = prosjek
)# A tibble: 4 × 4
dobna_skupina portal sm tv
<chr> <dbl> <dbl> <dbl>
1 18-19 4.9 3.5 4.6
2 20-21 4.9 3.4 4.8
3 22-23 5 3.5 4.1
4 24+ 5.2 3.3 4.4
Rezultat je tablica s jednim retkom po dobnoj skupini i jednim stupcem po tipu medija. Ovo je format koji biste stavili u izvještaj ili akademski rad jer je lako čitljiv.
Argumenti su zrcalni u odnosu na pivot_longer():
names_from je stupac čije će vrijednosti postati imena novih stupaca.
values_from je stupac čije će vrijednosti popuniti nove stupce.
12.4 Primjer s minutama korištenja
Isti obrazac primjenjujemo i na podatke o korištenju medija.
# Pretvorba minuta korištenja u dugački format
koristenje_long <- clean |>
select(id, dob, spol, dobna_skupina, tv_minuta, portal_min, sm_min) |>
pivot_longer(
cols = c(tv_minuta, portal_min, sm_min),
names_to = "medij",
values_to = "minuta"
) |>
mutate(
medij = case_when(
medij == "tv_minuta" ~ "TV",
medij == "portal_min" ~ "Portali",
medij == "sm_min" ~ "Društvene mreže"
)
)
# Prosječno korištenje po tipu medija
koristenje_long |>
group_by(medij) |>
summarise(
prosjek = round(mean(minuta, na.rm = TRUE), 1),
medijan = median(minuta, na.rm = TRUE),
.groups = "drop"
) |>
arrange(desc(prosjek))# A tibble: 3 × 3
medij prosjek medijan
<chr> <dbl> <dbl>
1 Društvene mreže 121. 124
2 Portali 42.9 43
3 TV 34.7 17
Društvene mreže dominiraju s velikim razmakom. TV je daleko na dnu. Ovi podaci su za studentsku populaciju, pa ne iznenađuju, ali upravo ovakve tablice čine temelj svakog izvještaja o medijskim navikama.
Zapamtite sljedeće pravilo. pivot_longer() koristite kad želite pretvoriti podatke iz oblika čitljivog za ljude u oblik pogodan za analizu i vizualizaciju. pivot_wider() koristite kad želite rezultate pretvoriti natrag u oblik čitljiv za ljude (za tablice u izvještajima). Tipičan radni tok uključuje sljedeće korake: učitajte podatke, pretvorite u dugački format, analizirajte i pretvorite rezultate u široki format za prezentaciju.
13 Spajanje tablica pomoću left_join()
U stvarnim istraživanjima, podaci rijetko dolaze u jednoj tablici. Možda imate jednu tablicu s demografskim podacima ispitanika i drugu s rezultatima eksperimenta. Ili jednu tablicu s podacima o člancima i drugu s podacima o komentarima. Da biste ih analizirali zajedno, morate ih spojiti.
Kreirajmo pomoćnu tablicu za demonstraciju.
# Tablica s informacijama o gradovima
gradovi_info <- tibble(
grad = c("Zagreb", "Split", "Rijeka", "Osijek", "Zadar", "Dubrovnik",
"Slavonski Brod", "Pula", "Karlovac", "Varaždin", "Šibenik", "Sisak"),
regija = c("Središnja", "Dalmacija", "Primorje", "Slavonija", "Dalmacija", "Dalmacija",
"Slavonija", "Istra", "Središnja", "Sjever", "Dalmacija", "Središnja"),
populacija_tis = c(770, 160, 108, 96, 70, 41, 50, 52, 46, 41, 34, 33)
)
gradovi_info# A tibble: 12 × 3
grad regija populacija_tis
<chr> <chr> <dbl>
1 Zagreb Središnja 770
2 Split Dalmacija 160
3 Rijeka Primorje 108
4 Osijek Slavonija 96
5 Zadar Dalmacija 70
6 Dubrovnik Dalmacija 41
7 Slavonski Brod Slavonija 50
8 Pula Istra 52
9 Karlovac Središnja 46
10 Varaždin Sjever 41
11 Šibenik Dalmacija 34
12 Sisak Središnja 33
Sada možemo spojiti ovu tablicu s našim čistim podacima da svakom ispitaniku dodamo informaciju o regiji i populaciji grada.
clean_s_regijom <- clean |>
left_join(gradovi_info, by = "grad")
clean_s_regijom |>
select(id, grad, regija, populacija_tis, sm_min) |>
head(10)# A tibble: 10 × 5
id grad regija populacija_tis sm_min
<dbl> <chr> <chr> <dbl> <dbl>
1 1 Zagreb Središnja 770 59
2 2 Zadar Dalmacija 70 101
3 3 Zagreb Središnja 770 177
4 4 Split Dalmacija 160 71
5 5 Zagreb Središnja 770 161
6 6 Zagreb Središnja 770 155
7 7 Zagreb Središnja 770 114
8 8 Karlovac Središnja 46 119
9 9 Split Dalmacija 160 56
10 10 Osijek Slavonija 96 40
Funkcija left_join() spaja dvije tablice po zajedničkom stupcu (ovdje grad). Za svaki redak u lijevoj tablici (clean), traži podudarajući redak u desnoj tablici (gradovi_info) i dodaje stupce iz desne tablice. Ako nema podudaranja (na primjer, grad koji nije u tablici gradovi_info), dobivamo NA.
Argument by = "grad" specificira koji stupac koristimo za podudaranje. Ako se stupac za spajanje različito zove u dvjema tablicama, koristimo sintaksu by = c("ime_lijevo" = "ime_desno").
Sad možemo analizirati podatke po regijama.
clean_s_regijom |>
group_by(regija) |>
summarise(
n = n(),
prosjek_sm = round(mean(sm_min), 1),
prosjek_trust_sm = round(mean(trust_sm), 1),
.groups = "drop"
) |>
filter(!is.na(regija)) |>
arrange(desc(prosjek_sm))# A tibble: 6 × 4
regija n prosjek_sm prosjek_trust_sm
<chr> <int> <dbl> <dbl>
1 Istra 10 140 3
2 Dalmacija 74 132. 3.2
3 Slavonija 31 127. 3.5
4 Središnja 110 115. 3.6
5 Primorje 18 112. 3.4
6 Sjever 7 79 3.3
Ovo je moć spajanja tablica — informacija koja je bila u zasebnoj tablici sada je dio naše analize i omogućuje grupiranje po varijabli koja nije postojala u izvornim podacima.
13.1 Vrste joinova
left_join() je daleko najčešći join i jedini koji ćete trebati u većini situacija. Ali vrijedi znati da postoje i drugi.
left_join(a, b) zadržava sve retke iz a, dodaje podudarajuće iz b. Ako nema podudaranja, NA.
inner_join(a, b) zadržava samo retke koji postoje u obje tablice.
full_join(a, b) zadržava sve retke iz obje tablice, s NA gdje nema podudaranja.
anti_join(a, b) zadržava retke iz a koji nemaju podudaranje u b. Korisno za pronalaženje nepodudarajućih zapisa.
# Ima li ispitanika iz gradova koji nisu u našoj tablici?
clean |>
anti_join(gradovi_info, by = "grad") |>
count(grad)# A tibble: 0 × 2
# ℹ 2 variables: grad <chr>, n <int>
anti_join() je odličan dijagnostički alat jer otkriva retke koji se ne mogu spojiti. U ovom slučaju vidimo gradove koji postoje u anketi ali ne u našoj tablici gradova.
14 Stringovi — osnove rada s tekstom
U komunikologiji se često radi s tekstualnim podacima, kao što su imena platformi, naslovi članaka i otvoreni odgovori u anketama. Paket stringr (dio tidyverse) pruža konzistentan skup funkcija za rad s tekstom.
Već smo koristili str_to_lower() i str_detect(). Pogledajmo još nekoliko korisnih funkcija.
# Stupac s platformama je slobodan tekst s više unosa
raw |>
select(id_respondenta, koje_platforme_koristi) |>
head(5)# A tibble: 5 × 2
id_respondenta koje_platforme_koristi
<dbl> <chr>
1 1 Snapchat, WhatsApp, Facebook
2 2 Facebook, YouTube
3 3 WhatsApp
4 4 Pinterest, LinkedIn
5 5 Viber, Snapchat, Reddit
# Koliko ispitanika koristi Instagram (bilo gdje u tekstu)?
raw |>
mutate(koristi_instagram = str_detect(koje_platforme_koristi, "Instagram")) |>
count(koristi_instagram)# A tibble: 2 × 2
koristi_instagram n
<lgl> <int>
1 FALSE 211
2 TRUE 39
14.1 Brojanje i izdvajanje uzoraka
# Koliko platformi svaki ispitanik navodi (brojeći zareze + 1)?
raw |>
mutate(
navedeno_platformi = str_count(koje_platforme_koristi, ",") + 1
) |>
select(id_respondenta, koje_platforme_koristi, navedeno_platformi) |>
head(8)# A tibble: 8 × 3
id_respondenta koje_platforme_koristi navedeno_platformi
<dbl> <chr> <dbl>
1 1 Snapchat, WhatsApp, Facebook 3
2 2 Facebook, YouTube 2
3 3 WhatsApp 1
4 4 Pinterest, LinkedIn 2
5 5 Viber, Snapchat, Reddit 3
6 6 Pinterest, YouTube 2
7 7 WhatsApp 1
8 8 Reddit, Pinterest, WhatsApp, Twitter/X 4
Funkcija str_count() broji koliko se puta uzorak pojavljuje u tekstu. Budući da su platforme odvojene zarezima, broj zareza plus jedan daje broj navedenih platformi. Ovo je primjer kako tekstualne operacije pomažu u izvlačenju numeričkih informacija iz nestrukturiranih podataka.
14.2 Zamjena i čišćenje teksta
# Zamjena "Twitter/X" s "X" za konzistentnost
raw |>
mutate(
platforme_clean = str_replace(koje_platforme_koristi, "Twitter/X", "X")
) |>
filter(str_detect(koje_platforme_koristi, "Twitter")) |>
select(koje_platforme_koristi, platforme_clean) |>
head(5)# A tibble: 5 × 2
koje_platforme_koristi platforme_clean
<chr> <chr>
1 Reddit, Pinterest, WhatsApp, Twitter/X Reddit, Pinterest, WhatsApp, X
2 YouTube, Twitter/X, Pinterest, WhatsApp YouTube, X, Pinterest, WhatsApp
3 Pinterest, Instagram, Telegram, Twitter/X Pinterest, Instagram, Telegram, X
4 Reddit, Twitter/X, Pinterest Reddit, X, Pinterest
5 LinkedIn, Telegram, Snapchat, Twitter/X LinkedIn, Telegram, Snapchat, X
Funkcija str_replace() zamjenjuje prvo pojavljivanje uzorka, a str_replace_all() zamjenjuje sva pojavljivanja. Funkcija str_trim() uklanja razmake s početka i kraja teksta, što je korisno kad ispitanici slučajno unesu razmak.
15 Sve zajedno — kompletna analiza od sirovih do gotovih podataka
Zaokružimo ovo predavanje tako da napišemo kompletnu analizu koja prolazi kroz sve faze, a to su učitavanje, čišćenje, transformacija, analiza i prezentacija rezultata. Ovo je obrazac koji ćete ponavljati u svakom projektu.
# FAZA 1: Učitavanje i čišćenje
anketa_clean <- read_csv("../resources/datasets/media_habits_raw.csv") |>
clean_names() |>
mutate(
# Čišćenje spola
spol = case_when(
str_to_lower(spol) %in% c("ženski", "ž", "zensko", "female") ~ "ženski",
str_to_lower(spol) %in% c("muški", "m", "musko", "male") ~ "muški",
.default = NA_character_
),
# Čišćenje godine studija
godina = case_when(
str_to_lower(godina_studija) %in% c("1", "1.", "prva") ~ 1L,
str_to_lower(godina_studija) %in% c("2", "2.", "druga") ~ 2L,
str_to_lower(godina_studija) %in% c("3", "3.", "treća", "treca") ~ 3L,
str_to_lower(godina_studija) %in% c("4", "4.") ~ 4L,
str_to_lower(godina_studija) %in% c("5", "5.") ~ 5L,
.default = NA_integer_
),
# TV minute: text -> broj
tv_min = case_when(
tv_min_dan == "ne gledam" ~ 0,
tv_min_dan == "" ~ NA_real_,
.default = as.numeric(tv_min_dan)
),
# Dobna skupina
dobna_sk = case_when(
dob < 20 ~ "18-19",
dob < 22 ~ "20-21",
dob < 24 ~ "22-23",
dob >= 24 ~ "24+"
)
) |>
# Odabir i preimenovanje konačnih stupaca
select(
id = id_respondenta,
dob, spol, grad, godina,
tv_min,
portal_min = portali_min_dan,
sm_min = drustvene_mreze_min_dan,
trust_tv = povjerenje_tv_1_10,
trust_portal = povjerenje_portali_1_10,
trust_sm = povjerenje_drustvene_mreze_1_10,
vijesti = koliko_cesto_prati_vijesti,
dobna_sk
)
glimpse(anketa_clean)Rows: 250
Columns: 13
$ id <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17…
$ dob <dbl> 20, 27, 27, 18, 25, 26, 28, 26, 22, 21, 22, 27, 20, 20, 2…
$ spol <chr> "ženski", "muški", "muški", "ženski", "ženski", "muški", …
$ grad <chr> "Zagreb", "Zadar", "Zagreb", "Split", "Zagreb", "Zagreb",…
$ godina <int> 2, 3, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 1, 2, 2, 1, 1, 1, 2, …
$ tv_min <dbl> 0, 0, 65, NA, NA, 91, 91, 0, 76, 66, NA, 109, 0, 0, 56, 0…
$ portal_min <dbl> 40, 20, 0, 11, 32, 25, 81, 28, 37, 5, 38, 44, 26, 65, 35,…
$ sm_min <dbl> 59, 101, 177, 71, 161, 155, 114, 119, 56, 40, 129, 95, 72…
$ trust_tv <dbl> 2, 3, 4, 5, 5, 4, 3, 6, 6, 7, 2, 7, 7, 5, 4, 2, 5, 3, 4, …
$ trust_portal <dbl> 6, 5, 6, 6, 3, 7, 7, 1, 7, 5, 6, 5, 5, 5, 4, 6, 4, 5, 6, …
$ trust_sm <dbl> 4, 3, 1, 4, 4, 7, 2, 3, 4, 6, 1, 2, 3, 5, 2, 2, 4, 3, 6, …
$ vijesti <chr> "više puta dnevno", "nekoliko puta tjedno", "više puta dn…
$ dobna_sk <chr> "20-21", "24+", "24+", "18-19", "24+", "24+", "24+", "24+…
# FAZA 2: Provjera
# Koliko NA po stupcu?
anketa_clean |>
summarise(across(everything(), ~sum(is.na(.x)))) |>
pivot_longer(everything(), names_to = "stupac", values_to = "n_NA") |>
filter(n_NA > 0)# A tibble: 1 × 2
stupac n_NA
<chr> <int>
1 tv_min 41
# FAZA 3: Deskriptivna analiza
# Korištenje medija po dobnoj skupini
anketa_clean |>
group_by(dobna_sk) |>
summarise(
n = n(),
across(
c(sm_min, portal_min, tv_min),
list(M = ~round(mean(.x, na.rm = TRUE), 1)),
.names = "{.col}_{.fn}"
),
.groups = "drop"
)# A tibble: 4 × 5
dobna_sk n sm_min_M portal_min_M tv_min_M
<chr> <int> <dbl> <dbl> <dbl>
1 18-19 73 122 42.3 33
2 20-21 61 120 43.6 34.4
3 22-23 47 120. 42.9 35.6
4 24+ 69 122. 42.9 36.1
# Povjerenje po tipu medija (dugački format za lakšu usporedbu)
anketa_clean |>
pivot_longer(
cols = starts_with("trust"),
names_to = "medij",
values_to = "povjerenje",
names_prefix = "trust_"
) |>
mutate(
medij = case_when(
medij == "tv" ~ "Televizija",
medij == "portal" ~ "Web portali",
medij == "sm" ~ "Društvene mreže"
)
) |>
group_by(medij) |>
summarise(
M = round(mean(povjerenje), 2),
SD = round(sd(povjerenje), 2),
Med = median(povjerenje),
.groups = "drop"
) |>
arrange(desc(M))# A tibble: 3 × 4
medij M SD Med
<chr> <dbl> <dbl> <dbl>
1 Web portali 5.01 1.73 5
2 Televizija 4.48 1.99 4
3 Društvene mreže 3.41 1.72 3
# Tko prati vijesti, a tko ne?
anketa_clean |>
mutate(
cesto_prati = vijesti %in% c("više puta dnevno", "jednom dnevno")
) |>
group_by(cesto_prati) |>
summarise(
n = n(),
prosjek_dob = round(mean(dob), 1),
prosjek_sm = round(mean(sm_min), 1),
prosjek_trust_portal = round(mean(trust_portal), 1),
prosjek_trust_sm = round(mean(trust_sm), 1),
.groups = "drop"
)# A tibble: 2 × 6
cesto_prati n prosjek_dob prosjek_sm prosjek_trust_portal prosjek_trust_sm
<lgl> <int> <dbl> <dbl> <dbl> <dbl>
1 FALSE 109 21.6 120. 5.2 3.3
2 TRUE 141 21.9 122. 4.9 3.5
Ova kompletna analiza, od učitavanja sirovih podataka do gotovih tablica, stane u manje od 80 redova koda. Svaki korak je dokumentiran, ponovljiv i čitljiv. Ako sutra dobijete ažurirane podatke s još 100 ispitanika, pokrenete istu skriptu i dobijete ažurirane rezultate. To je suština ponovljive analize.
Cilj čišćenja podataka nije savršenstvo. Cilj je da od neurednog, nekonzistentnog i djelomično nepoznatog skupa podataka stvorite skup koji je dovoljno uredan i dokumentiran da možete s povjerenjem raditi statističku analizu i transparentno komunicirati svaki izbor koji ste napravili.
Čišćenje i priprema podataka oduzima 80% vremena u bilo kojoj analizi. Stvarni podaci su gotovo uvijek neuredni i zahtijevaju sistematično čišćenje prije ikakve statističke analize.
Funkcija
clean_names()iz paketa janitor standardizira imena stupaca u snake_case format. Koristite je odmah nakon učitavanja svakog dataseta.filter()odabire retke po uvjetu. Automatski odbacuje retke s NA. Koristite%in%za provjeru pripadnosti skupu,between()za raspone istr_detect()za pretraživanje teksta.select()odabire, uklanja i preuređuje stupce. Pomoćne funkcijestarts_with(),ends_with(),contains()iwhere()omogućuju pametan odabir.rename()mijenja imena bez gubitka stupaca.mutate()kreira nove stupce i transformira postojeće.case_when()je alat za složeno rekodiranje.if_else()za binarno. Razlika između 0 i NA je konceptualno važna.arrange()sortira retke.desc()za silazni smjer. NA uvijek na kraj.group_by() |> summarise()je temeljni obrazac za izračun statistika po grupama. Uvijek dodajte.groups = "drop".count()je kratica za prebrojavanje.across()primjenjuje istu operaciju na više stupaca odjednom. Kombinira se i sasummarise()i smutate().pivot_longer()pretvara stupce u redove (široki u dugački format).pivot_wider()pretvara redove u stupce. Dugački format je pogodan za analizu i vizualizaciju, široki za prezentaciju.left_join()spaja dvije tablice po zajedničkom stupcu. Koristite ga kad trebate kombinirati podatke iz više izvora.stringr funkcije (
str_detect(),str_to_lower(),str_replace(),str_count()) omogućuju rad s tekstualnim podacima. Bitne za čišćenje anketnih podataka.Svaka analiza ima jasne faze, a to su učitavanje, čišćenje, provjera, analiza i prezentacija. Dokumentirajte svaki korak i svaki izbor (osobito koliko redova gubite filtiranjem).
Sljedeći tjedan bavimo se deskriptivnom statistikom: mjerama centralne tendencije (prosjek, medijan, mod), mjerama varijabilnosti (varijanca, standardna devijacija, IQR), korelacijama i standardnim rezultatima (z-scores). Sve ćemo raditi kroz summarise() i group_by() koje ste upravo naučili.
Za pripremu napravite sljedeće:
- Ponovite kompletni pipeline čišćenja iz ovog predavanja. Pokrenite ga red po red i provjerite da razumijete svaki korak.
- Pokušajte odgovoriti na pitanje: razlikuje li se prosječno povjerenje u društvene mreže između ispitanika koji prate vijesti često i onih koji ne prate? (Hint:
mutate()za kreiranje binarne varijable,group_by() |> summarise()za usporedbu.) - Pretvorite podatke o korištenju (TV, portali, društvene mreže) u dugački format pomoću
pivot_longer()i izračunajte prosječno korištenje po tipu medija i spolu. - Pročitajte poglavlje 5 iz knjige Navarro (Learning Statistics with R) o deskriptivnoj statistici. Fokusirajte se na koncepte, ne na R kod (jer knjiga koristi base R).
16 Dodatno čitanje
Obavezno
Wickham, H. & Grolemund, G. (2023). R for Data Science (2nd edition), Chapters 4, 5 i 6. Besplatno dostupno na r4ds.hadley.nz. Poglavlje 4 pokriva transformaciju podataka, poglavlje 5 organizaciju radnog toka, poglavlje 6 preoblikovanje podataka s pivot funkcijama.
Navarro, D. (2018). Learning Statistics with R, Chapters 4 i 7. Besplatno dostupno na learningstatisticswithr.com. Pokrivaju sličan teren u base R sintaksi.
Preporučeno
Wickham, H. (2014). Tidy Data. Journal of Statistical Software, 59(10). Besplatno dostupno na vita.had.co.nz/papers/tidy-data.pdf. Klasičan rad koji definira princip urednih podataka.
Firke, S. (2023). janitor: Simple Tools for Examining and Cleaning Dirty Data. Dokumentacija paketa na sfirke.github.io/janitor. Osim clean_names(), paket sadrži i tabyl() za brze tablice frekvencija i remove_empty() za uklanjanje praznih redova i stupaca.
17 Pojmovnik
| Pojam | Objašnjenje |
|---|---|
| dplyr | R paket iz tidyverse ekosustava za manipulaciju podacima. Sadrži glagole filter(), select(), mutate(), summarise(), arrange(), group_by() i druge. |
filter() |
dplyr glagol za odabir redova koji zadovoljavaju logički uvjet. Automatski odbacuje retke s NA u uvjetu. |
select() |
dplyr glagol za odabir, uklanjanje i preuređivanje stupaca. Podržava pomoćne funkcije poput starts_with(), contains() i where(). |
mutate() |
dplyr glagol za kreiranje novih stupaca ili transformaciju postojećih. Novi stupci mogu referirati na upravo kreirane. |
arrange() |
dplyr glagol za sortiranje redova po vrijednostima stupaca. desc() za silazno sortiranje. |
summarise() |
dplyr glagol za sažimanje podataka u jednu vrijednost po grupi (ili za cijeli dataset). Koristi se s agregatnim funkcijama poput mean(), sd(), n(). |
group_by() |
dplyr glagol koji dijeli podatke u grupe po jednoj ili više varijabli. Sve naknadne operacije se izvršavaju zasebno za svaku grupu. |
ungroup() |
dplyr glagol koji uklanja grupiranje. Koristite nakon group_by() |> mutate() da izbjegnete neočekivano ponašanje. |
count() |
Kratica za group_by() |> summarise(n = n()) |> ungroup(). Prebrojava opažanja po kategorijama. |
across() |
Funkcija za primjenu iste operacije na više stupaca odjednom. Radi unutar summarise() i mutate(). |
rename() |
dplyr glagol za preimenovanje stupaca bez gubitka ostalih. Sintaksa: rename(novo = staro). |
relocate() |
dplyr glagol za premještanje stupaca na drugu poziciju u datasetu. |
case_when() |
Funkcija za složeno rekodiranje s više uvjeta. Svaki uvjet ima oblik uvjet ~ vrijednost. Provjerava uvjete redom. |
if_else() |
Funkcija za binarno rekodiranje. Prima uvjet, vrijednost za TRUE i vrijednost za FALSE. |
between() |
Pomoćna funkcija: provjera je li vrijednost unutar raspona. Kratica za x >= left & x <= right. |
str_detect() |
stringr funkcija: provjerava sadrži li tekst zadani uzorak. Vraća TRUE/FALSE. |
str_to_lower() |
stringr funkcija: pretvara tekst u mala slova. Korisna za standardizaciju. |
str_replace() |
stringr funkcija: zamjenjuje prvo pojavljivanje uzorka u tekstu. str_replace_all() zamjenjuje sva. |
str_count() |
stringr funkcija: broji pojavljivanja uzorka u tekstu. |
pivot_longer() |
tidyr funkcija za pretvaranje stupaca u redove (široki u dugački format). Ključni argumenti: cols, names_to, values_to. |
pivot_wider() |
tidyr funkcija za pretvaranje redova u stupce (dugački u široki format). Ključni argumenti: names_from, values_from. |
left_join() |
dplyr funkcija za spajanje dviju tablica po zajedničkom stupcu. Zadržava sve retke iz lijeve tablice. |
inner_join() |
Spajanje koje zadržava samo retke koji postoje u obje tablice. |
anti_join() |
Spajanje koje zadržava retke iz lijeve tablice koji nemaju podudaranje u desnoj. Dijagnostički alat. |
clean_names() |
janitor funkcija: pretvara imena stupaca u snake_case. Uklanja razmake, zagrade i specijalne znakove. |
drop_na() |
tidyr funkcija za uklanjanje redova s NA. Može se primijeniti na cijeli dataset ili specifične stupce. |
| Tidy data (uredni podaci) | Princip organizacije podataka: svaki redak je opažanje, svaki stupac varijabla, svaka ćelija vrijednost. |
| Široki format | Organizacija podataka u kojoj su različita mjerenja iste varijable raspoređena u zasebne stupce. Čitljiv za ljude. |
| Dugački format | Organizacija podataka u kojoj su različita mjerenja u zasebnim redovima s identifikacijskim stupcem. Pogodan za analizu i vizualizaciju. |
| Pipeline | Niz operacija spojenih pipe operatorom (|>) koji transformira podatke korak po korak. |
| Sirovi podaci (raw data) | Podaci u izvornom obliku, prije čišćenja. Ne bi ih trebalo mijenjati izravno. |
| Čisti podaci (clean data) | Podaci nakon standardizacije, rekodiranja i provjere. Spremni za analizu. |
| Rekodiranje | Pretvaranje vrijednosti varijable u standardizirani oblik (npr. svih varijanti spola u “ženski”/“muški”). |