U radu s podacima analiza nikad ne počinje modelom, regresijom ili grafom. Prvi i često najvažniji korak je priprema podataka.
Taj proces u informatici i analitici naziva se ETL – skraćenica od:
ETL predstavlja temelj svake ozbiljne analize podataka.
Zašto je ETL potreban?
U stvarnim sustavima podaci:
Primjer:
Bez ETL faze:
ETL kao profesionalni standard
U poslovnim sustavima ETL:
U znanstvenom radu ETL:
ETL nije “tehnički detalj”
Vrlo često se događa da:
Zato ETL nije pomoćna faza, nego:
Strukturni temelj svake ozbiljne analize podataka.
U ovom poglavlju fokus je na:
Cilj nije samo “da kod radi”, nego da:
R je statistički programski jezik namijenjen analizi podataka, modeliranju i vizualizaciji. RStudio je integrirano razvojno okruženje (IDE) koje omogućuje organiziran rad kroz:
Detaljnije upute naći ćete u Uvodu u R i RStudio.
Što je RMarkdown?
RMarkdown je format dokumenta koji omogućuje kombiniranje:
Datoteka ima nastavak: .Rmd
Kod se dodaje unutar tzv. chunkova:
summary(data)
RMarkdown se temelji na paketu rmarkdown i
Pandoc sustavu za pretvorbu dokumenata.
Što je Quarto?
Quarto je proširena verzija koncepta RMarkdowna.
Datoteka ima nastavak: .qmd
Prednosti Quarta:
Kod u Quartu izgleda isto, dodaje se unutar tzv. chunkova:
summary(data)
Usporedba RMarkdowna i Quarta
| Karakteristika | RMarkdown | Quarto |
|---|---|---|
| Nastavak | .Rmd |
.qmd |
| Podrška jezika | primarno R | R, Python, Julia |
| Projektna struktura | ograničena | napredna |
| Web publikacija | moguće | prirodno integrirano |
| Budući razvoj | održavanje | aktivni razvoj (preporučeno) |
| YAML struktura | jednostavna | fleksibilnija |
Kad koristiti koji?
RMarkdown je dobar za:
Quarto je bolji izbor za:
Reproducibilnost i dokumentiranje rada
Bez obzira koristimo li RMarkdown ili Quarto, važno je:
Provjera verzije:
sessionInfo()
## R version 4.4.2 (2024-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 10 x64 (build 19045)
##
## Matrix products: default
##
##
## locale:
## [1] LC_COLLATE=Croatian_Croatia.utf8 LC_CTYPE=Croatian_Croatia.utf8
## [3] LC_MONETARY=Croatian_Croatia.utf8 LC_NUMERIC=C
## [5] LC_TIME=Croatian_Croatia.utf8
##
## time zone: Europe/Zagreb
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices datasets utils methods base
##
## loaded via a namespace (and not attached):
## [1] digest_0.6.39 R6_2.6.1 fastmap_1.2.0 xfun_0.56
## [5] cachem_1.1.0 knitr_1.51 htmltools_0.5.9 rmarkdown_2.30
## [9] lifecycle_1.0.5 cli_3.6.5 sass_0.4.10 renv_1.1.1
## [13] jquerylib_0.1.4 compiler_4.4.2 rstudioapi_0.18.0 tools_4.4.2
## [17] evaluate_1.0.5 bslib_0.10.0 yaml_2.3.12 jsonlite_2.0.0
## [21] rlang_1.1.7
Komentari
# Ovo je komentar u R-u
x <- 10 # dodjeljujemo vrijednost 10 varijabli x
U R-u se podaci pohranjuju u nekoliko osnovnih struktura. Važno je razlikovati ih jer se ETL koraci (učitavanje, čišćenje, transformacija) često svode na pretvaranje podataka iz jednog “spremnika” u drugi, uz promjenu tipova varijabli i reorganizaciju stupaca/redaka.
Ako trebate detaljniji uvod (s puno primjera i objašnjenjem rada u RStudio okruženju), koristite: Uvod u R i RStudio
Vektor je najosnovniji format u R-u: to je niz vrijednosti istog tipa (npr. sve numeričke ili sve tekstualne).
dob,
bodovi, spol)names()),
što olakšava rad i referenciranjev <- c(2, 4, 6, 8)
names(v) <- c("a", "b", "c", "d")
v
## a b c d
## 2 4 6 8
Napomena (često zbunjuje): vektor ne može “miješati tipove” bez posljedica. Ako ubacite znak u numerički vektor, R će sve pretvoriti u tekst.
c(1, 2, "3") # sve postaje character
## [1] "1" "2" "3"
Matrica je dvodimenzionalni format: tablica s recima i stupcima, ali i dalje vrijedi pravilo: svi elementi moraju biti istog tipa.
m <- matrix(1:9, nrow = 3)
m
## [,1] [,2] [,3]
## [1,] 1 4 7
## [2,] 2 5 8
## [3,] 3 6 9
Korisno je imenovati retke/stupce:
rownames(m) <- paste0("r", 1:3)
colnames(m) <- paste0("c", 1:3)
m
## c1 c2 c3
## r1 1 4 7
## r2 2 5 8
## r3 3 6 9
Podatkovni okvir/Data frame je najčešći oblik podataka u analizi: tablica gdje su stupci varijable, a redci opažanja.
Ključna razlika u odnosu na matricu:
stupci mogu biti različitih tipova (npr. jedan stupac numerički, drugi tekstualni, treći faktor/datum).
df <- data.frame(
ime = c("Ana", "Ivan", "Marko"),
bodovi = c(85, 92, 78)
)
df
## ime bodovi
## 1 Ana 85
## 2 Ivan 92
## 3 Marko 78
Brzi pregled strukture:
str(df)
## 'data.frame': 3 obs. of 2 variables:
## $ ime : chr "Ana" "Ivan" "Marko"
## $ bodovi: num 85 92 78
Napomena za ETL: nakon učitavanja podataka, prvi
korak je provjeriti jesu li tipovi stupaca dobri (npr. je li datum
stvarno Date, je li broj stvarno numeric,
itd.).
Pristup 2. retku podatkovnog okvira:
df[2,]
## ime bodovi
## 2 Ivan 92
Pristup 1. stupcu podatkovnog okvira:
df[,1]
## [1] "Ana" "Ivan" "Marko"
Pristup varijabli bodovi:
df$bodovi
## [1] 85 92 78
Pristup elementu u 3. retku i 2. stupcu podatkovnog okvira:
df[3,2]
## [1] 78
Tibble je modernija verzija data.frame
objekta, razvijena u okviru paketa tibble (dio
tidyverse ekosustava).
U praksi:
data.frame je “klasični” R objekttibble je njegova unaprijeđena i preglednija
verzijaAko koristimo dplyr, tidyr ili
readr, podaci se automatski učitavaju kao tibble.
library(tibble)
tb <- tibble(
ime = c("Ana", "Ivan", "Marko"),
bodovi = c(85, 92, 78)
)
tb
## # A tibble: 3 × 2
## ime bodovi
## <chr> <dbl>
## 1 Ana 85
## 2 Ivan 92
## 3 Marko 78
Razlika između data.frame i tibble
| Svojstvo | data.frame | tibble |
|---|---|---|
| Ispis u konzoli | ispisuje sve retke | prikazuje samo prvih 10 redaka |
| Automatska konverzija stringova u faktor | ponekad | nikad |
| Rad s nepostojećim stupcem | može vratiti NULL | baca grešku (sigurnije) |
| Indeksiranje | fleksibilno | strože i predvidljivije |
Primjer ispisa:
class(df)
## [1] "data.frame"
class(tb)
## [1] "tbl_df" "tbl" "data.frame"
Zašto je tibble važan u ETL procesu?
dplyrtidyrreadrPretvaranje između data.frame i tibble
as_tibble(df)
## # A tibble: 3 × 2
## ime bodovi
## <chr> <dbl>
## 1 Ana 85
## 2 Ivan 92
## 3 Marko 78
as.data.frame(tb)
## ime bodovi
## 1 Ana 85
## 2 Ivan 92
## 3 Marko 78
Lista je “spremnik za sve”: može sadržavati elemente različitih tipova i različitih dimenzija (vektore, matrice, data frame-ove, modele, grafove…).
lst <- list(v, m, df)
str(lst)
## List of 3
## $ : Named num [1:4] 2 4 6 8
## ..- attr(*, "names")= chr [1:4] "a" "b" "c" "d"
## $ : int [1:3, 1:3] 1 2 3 4 5 6 7 8 9
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:3] "r1" "r2" "r3"
## .. ..$ : chr [1:3] "c1" "c2" "c3"
## $ :'data.frame': 3 obs. of 2 variables:
## ..$ ime : chr [1:3] "Ana" "Ivan" "Marko"
## ..$ bodovi: num [1:3] 85 92 78
Možete dodati imena elemenata liste:
lst2 <- list(vektor = v, matrica = m, podaci = df)
lst2$matrica
## c1 c2 c3
## r1 1 4 7
## r2 2 5 8
## r3 3 6 9
lst2$podaci
## ime bodovi
## 1 Ana 85
## 2 Ivan 92
## 3 Marko 78
lst$podaci
## NULL
install.packages() je naredba za instalaciju paketa.
Naziv paketa koji instaliramo stavljamo u navodne znakove.
install.packages("psych")
install.packages("dplyr")
install.packages("tidyr")
install.packages("readr")
install.packages("readxl") # ili "openxlsx"
install.packages("DBI")
install.packages("RSQLite")
install.packages("nycflights13")
install.packages("lubridate")
install.packages("janitor")
install.packages("stringr")
psych je paket za psihometrijsku i deskriptivnu
statistiku.
Koristit ćemo ga za:
detaljne deskriptivne pokazatelje
(describe())
brzi pregled distribucija
izračun statističkih mjera u tabličnom obliku
dplyr je temeljni paket za manipulaciju podacima
(transform dio ETL-a).
Omogućuje:
filtriranje redaka (filter())
odabir stupaca (select())
sortiranje (arrange())
grupiranje i agregiranje (group_by(),
summarise())
kreiranje novih varijabli (mutate())
tidyr je paket za preoblikovanje podataka
(strukturiranje podataka u „tidy” oblik).
Koristit ćemo ga za:
pretvaranje širokog u dugi format
(pivot_longer())
pretvaranje dugog u široki (pivot_wider())
razdvajanje i spajanje varijabli
readr služi za brzo i pouzdano učitavanje tekstualnih
datoteka (CSV, TXT).
Prednosti:
automatsko prepoznavanje tipova varijabli
brže učitavanje od base R funkcija
jasne poruke o problemima u podacima
readxl služi za učitavanje Excel (.xlsx) datoteka bez
potrebe za Excelom instaliranim na računalu.
DBI je standardizirano sučelje za rad s bazama podataka
u R-u.
Omogućuje:
povezivanje s bazom
slanje SQL upita
dohvat podataka
Koristit ćemo ga zajedno s RSQLite.
RSQLite omogućuje rad sa SQLite bazama podataka (lagane,
lokalne baze).
Koristit ćemo ga za:
simulaciju rada s bazom podataka
spremanje obrađenih podataka
izvršavanje SQL upita
lubridate je paket za rad s datumima i vremenom.
Rad s datumima u base R-u često je:
osjetljiv na format
nepregledan
sklon greškama
lubridate pojednostavljuje:
parsiranje datuma
izdvajanje godine, mjeseca, dana
rad s vremenskim razlikama
konverziju vremenskih zona
janitor je paket za “higijenu podataka”.
Koristi se za:
standardizaciju naziva varijabli
brze frekvencijske tablice
uklanjanje praznih redaka/stupaca
čišćenje strukture podataka
najvažnija funkcija clean_names() - pretvara nazive
u mala slova, razmake zamjenjuje s donjom povlakom, uklanja posebne
znakove, uklanja dijakritike, osigurava sintaktičku ispravnost.
stringr se koristi za:
pretraživanje (str_detect)
zamjenu (str_replace)
izdvajanje (str_extract)
promjenu velikih/malih slova
Za više detalja, pogledajte dokumentaciju paketa.
Kako se ovi paketi uklapaju u ETL proces?
| ETL faza | Paketi |
|---|---|
| Extract | readr, readxl, DBI, RSQLite |
| Transform | janitor, dplyr, tidyr, lubridate, stringr |
| Load | DBI, RSQLite |
| Analiza i pregled | psych |
Naredbom library() pozivamo (aktiviramo) paket. Kao
argument upisujemo naziv paketa (bez navodnih znakova).
library(psych)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(tidyr)
library(readr)
library(readxl)
library(DBI)
library(RSQLite)
library(nycflights13)
library(lubridate)
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
library(janitor)
##
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
library(stringr)
U ETL procesu, Extract znači dohvat podataka iz različitih izvora:
Važno je razumjeti:
, ;
\t). ili
,)Prije nego dođemo u situaciju “ne mogu naći datoteku”, moramo se kratko upoznati s radnim direktorijem i projektnom strukturom. Dakle, datoteke čitamo relativnim putanjama unutar projekta.
getwd(), setwd()
preporuka strukture: data_raw/, data_clean/, scripts/, reports/
Minimalni kod-blok:
getwd()
list.files()
Ako se dokument s podacima nalazi u direktoriju projekta, možemo ga izravno učitati pozivom odgovarajuće funkcije i navođenjem naziva dokumenta kao argument te funkcije.
CSV je tekstualna datoteka gdje su vrijednosti odvojene separatorom (najčešće zarezom).
real_estate <- read.csv("Real_estate.csv")
head(real_estate)
## X Price Lot.Size Waterfront Age Land.Value New.Construct Central.Air
## 1 1 132500 0.09 0 42 50000 0 0
## 2 2 181115 0.92 0 0 22300 0 0
## 3 3 109000 0.19 0 133 7300 0 0
## 4 4 155000 0.41 0 13 18700 0 0
## 5 5 86060 0.11 0 0 15000 1 1
## 6 6 120000 0.68 0 31 14000 0 0
## Fuel.Type Heat.Type Sewer.Type Living.Area Pct.College Bedrooms Fireplaces
## 1 3 4 2 906 35 2 1
## 2 2 3 2 1953 51 3 0
## 3 2 3 3 1944 51 4 1
## 4 2 2 2 1944 51 3 1
## 5 2 2 3 840 51 2 0
## 6 2 2 2 1152 22 4 1
## Bathrooms Rooms
## 1 1.0 5
## 2 2.5 6
## 3 1.0 8
## 4 1.5 5
## 5 1.0 3
## 6 1.0 8
Objašnjenje
read.csv() je base R funkcija,data.frameVažno:
getwd())fileEncoding = "UTF-8")Alternativa (preporučena u modernom radu):
readr::read_csv()
Prednosti:
Excel je binarni format.
diamonds <- read_xlsx("Diamonds.xlsx")
head(diamonds)
## # A tibble: 6 × 12
## Carat.Size Color Clarity Depth Table Cut Report Price Log.Price Table.Depth
## <dbl> <chr> <chr> <dbl> <dbl> <chr> <chr> <dbl> <dbl> <dbl>
## 1 0.3 E VVS1 60 59 Excel… GIA 1000 6.91 -1
## 2 0.44 E VS2 61.9 58 Excel… GIA 1000 6.91 -3.9
## 3 0.31 E VVS1 61.3 58 Excel… GIA 1000 6.91 -3.3
## 4 0.66 K SI1 62.8 57 Excel… GIA 1000 6.91 -5.8
## 5 0.47 H VS2 59.1 64 Very … GIA 1000 6.91 4.9
## 6 0.4 G VS1 62 59 Excel… GIA 1000 6.91 -3
## # ℹ 2 more variables: Table.Depth.1 <dbl>, Test <dbl>
Objašnjenje
readxlVažno:
Excel datoteke često sadrže:
cyber_attacks <- read.csv(
"https://raw.githubusercontent.com/plotly/datasets/master/2014_apple_stock.csv"
)
head(cyber_attacks)
## AAPL_x AAPL_y
## 1 2014-01-02 77.44539
## 2 2014-01-03 77.04558
## 3 2014-01-06 74.89697
## 4 2014-01-07 75.85646
## 5 2014-01-08 75.09195
## 6 2014-01-09 76.20263
heart_rate <- read_csv(
"https://raw.githubusercontent.com/jasp-stats/jasp-data-library/main/Heart%20Rate/Heart%20Rate.csv"
)
## Rows: 800 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): Gender, Group
## dbl (1): Heart Rate
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(heart_rate)
## # A tibble: 6 × 3
## Gender Group `Heart Rate`
## <chr> <chr> <dbl>
## 1 Female Runners 119
## 2 Female Runners 84
## 3 Female Runners 89
## 4 Female Runners 119
## 5 Female Runners 127
## 6 Female Runners 111
Objašnjenje
read.table() je fleksibilnija funkcijaheader = TRUE znači da prvi red sadrži nazive
varijablisep = "\t")Prednost read.table():
Prednost read_csv():
Rizik:
Za txt podatke, najčešće pristupamo na sljedeći
način:
txt_url <- "http://sites.williams.edu/rdeveaux/files/2014/09/Saratoga.txt"
tmp <- tempfile(fileext = ".tsv")
download.file(txt_url, tmp, mode = "wb") # preuzima datoteku lokalno (robustnije)
data <- read.delim(tmp, sep = "\t", header = TRUE) # pretpostavlja tab separator; ako je space/više razmaka, treba read.table() ili sep=""
Tehnika poznaka i kao web-scraping. Ovdje se fokusiramo na dohvaćanje tablica. Ovdje ćemo pristupiti tablici koja sadrži podatke o 1000 najvećih poduzeća u Hrvatskoj, koju objavljuje i redovito ažurira Lider.hr.
library(rvest)
##
## Attaching package: 'rvest'
## The following object is masked from 'package:readr':
##
## guess_encoding
library(xml2)
library(rvest)
# učitamo HTML
webpage <- read_html("https://lidermedia.hr/lider-bi/tablice/1000-najvecih.html")
# pronađimo sve tablice u stranici
tables <- html_nodes(webpage, "table")
length(tables) # koliko ih ima
## [1] 1
df <- html_table(tables, fill = TRUE)
head(df)
## [[1]]
## # A tibble: 1,000 × 15
## `Rang 2023.` `Rang 2022.` Tvrtka `Grad/općina` Županija Sektor
## <int> <chr> <chr> <chr> <chr> <chr>
## 1 1 1 INA d.d. Zagreb 21. Gra… 22. N…
## 2 2 3 HRVATSKA ELEKTROPRIV… Zagreb 21. Gra… 6. En…
## 3 3 2 PRVO PLINARSKO DRUŠT… Vukovar 16. Vuk… 6. En…
## 4 4 6 KONZUM PLUS d.o.o. Zagreb 21. Gra… 43. T…
## 5 5 7 HEP PROIZVODNJA d.o.… Zagreb 21. Gra… 6. En…
## 6 6 4 MET CROATIA ENERGY T… Zagreb 21. Gra… 6. En…
## 7 7 9 LIDL HRVATSKA d.o.o.… Velika Gorica 1. Zagr… 43. T…
## 8 8 8 PETROL d.o.o. Zagreb 21. Gra… 22. N…
## 9 9 11 SPAR HRVATSKA d.o.o. Zagreb 21. Gra… 43. T…
## 10 10 14 ZAGREBAČKA BANKA d.d. Zagreb 21. Gra… 8. Fi…
## # ℹ 990 more rows
## # ℹ 9 more variables: `Ukupni prihod (mil €)` <dbl>,
## # `Promjena prihoda (%)` <chr>, `Bruto dobit/ gubitak (mil. €)` <dbl>,
## # `Promjena dobiti/gubitka (%)` <chr>, `Dobit u odnosu na prihode (%)` <chr>,
## # `Izvoz(mil. €)` <dbl>, `Uvoz(mil. €)` <dbl>, `Broj zaposlenih` <int>,
## # `Prosječna neto plaća (€)` <int>
U poslovnom svijetu, veliki podaci se rijetko čuvaju samo u CSV-u. Većina poslovnih sustava koristi baze podataka.
SQLite je lagana lokalna baza (bez servera).
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "diamonds", diamonds)
dbListTables(con)
db_data <- dbGetQuery(con, "SELECT carat, price FROM diamonds LIMIT 5")
Objašnjenje
dbConnect() otvara vezu prema bazi:memory: znači da je baza privremenadbWriteTable() sprema podatke u bazudbGetQuery() izvršava SQL upitR + SQL + ETL pipeline
U realnim sustavima:
Javne institucije nude API pristup podacima.
library(eurostat)
eu_data <- get_eurostat("nama_10_gdp", time_format = "date")
## Dataset query already saved in cache_list.json...
## Reading cache file C:\Users\Korisnik\AppData\Local\Temp\Rtmp0S7mGT/eurostat/91181ca47edc7ac431bd7af54f9a9651.rds
## Table nama_10_gdp read from cache file: C:\Users\Korisnik\AppData\Local\Temp\Rtmp0S7mGT/eurostat/91181ca47edc7ac431bd7af54f9a9651.rds
head(eu_data)
## # A tibble: 6 × 6
## freq unit na_item geo TIME_PERIOD values
## <chr> <chr> <chr> <chr> <date> <dbl>
## 1 A CLV05_MEUR B1G AL 1995-01-01 3422.
## 2 A CLV05_MEUR B1G AL 1996-01-01 3676.
## 3 A CLV05_MEUR B1G AL 1997-01-01 3279.
## 4 A CLV05_MEUR B1G AL 1998-01-01 3538
## 5 A CLV05_MEUR B1G AL 1999-01-01 4031.
## 6 A CLV05_MEUR B1G AL 2000-01-01 4265.
Objašnjenje
get_eurostat() dohvaća podatke putem API-jatime_format = "date" odmah konvertira
vremensku varijabluPrednosti:
Mnogi paketi sadrže skupove podataka za vježbu.
library(nycflights13)
data(flights)
head(flights)
## # A tibble: 6 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <dbl> <int> <int>
## 1 2013 1 1 517 515 2 830 819
## 2 2013 1 1 533 529 4 850 830
## 3 2013 1 1 542 540 2 923 850
## 4 2013 1 1 544 545 -1 1004 1022
## 5 2013 1 1 554 600 -6 812 837
## 6 2013 1 1 554 558 -4 740 728
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## # tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## # hour <dbl>, minute <dbl>, time_hour <dttm>
Objašnjenje
nycflights13 je edukativni dataset
sadrži podatke o letovima iz New Yorka
idealan za:
Prednost:
Nakon učitavanja podataka, ne prelazimo odmah na analizu. Prvo radimo inicijalni pregled (data audit).
Cilj:
Klasični (base R) pristup
str(diamonds)
## tibble [2,690 × 12] (S3: tbl_df/tbl/data.frame)
## $ Carat.Size : num [1:2690] 0.3 0.44 0.31 0.66 0.47 0.4 0.36 0.52 0.53 0.43 ...
## $ Color : chr [1:2690] "E" "E" "E" "K" ...
## $ Clarity : chr [1:2690] "VVS1" "VS2" "VVS1" "SI1" ...
## $ Depth : num [1:2690] 60 61.9 61.3 62.8 59.1 62 61.3 61.7 59.4 61.5 ...
## $ Table : num [1:2690] 59 58 58 57 64 59 57 61 59 60 ...
## $ Cut : chr [1:2690] "Excellent" "Excellent" "Excellent" "Excellent" ...
## $ Report : chr [1:2690] "GIA" "GIA" "GIA" "GIA" ...
## $ Price : num [1:2690] 1000 1000 1000 1000 1000 ...
## $ Log.Price : num [1:2690] 6.91 6.91 6.91 6.91 6.91 ...
## $ Table.Depth : num [1:2690] -1 -3.9 -3.3 -5.8 4.9 -3 -4.3 -0.7 -0.4 -1.5 ...
## $ Table.Depth.1: num [1:2690] 0.983 0.937 0.946 0.908 1.083 ...
## $ Test : num [1:2690] 0 1 0 2 2 2 0 0 0 2 ...
summary(diamonds)
## Carat.Size Color Clarity Depth
## Min. :0.3000 Length:2690 Length:2690 Min. :56.40
## 1st Qu.:0.6000 Class :character Class :character 1st Qu.:61.00
## Median :0.9000 Mode :character Mode :character Median :61.90
## Mean :0.8701 Mean :61.71
## 3rd Qu.:1.0600 3rd Qu.:62.50
## Max. :2.0200 Max. :64.30
## Table Cut Report Price
## Min. :53.00 Length:2690 Length:2690 Min. : 1000
## 1st Qu.:56.00 Class :character Class :character 1st Qu.: 1801
## Median :58.00 Mode :character Mode :character Median : 3604
## Mean :57.86 Mean : 3971
## 3rd Qu.:59.00 3rd Qu.: 5544
## Max. :65.00 Max. :10000
## Log.Price Table.Depth Table.Depth.1 Test
## Min. :6.908 Min. :-10.800 Min. :0.8307 Min. :0.0000
## 1st Qu.:7.496 1st Qu.: -5.800 1st Qu.:0.9076 1st Qu.:0.0000
## Median :8.190 Median : -4.200 Median :0.9325 Median :0.0000
## Mean :8.084 Mean : -3.851 Mean :0.9382 Mean :0.6004
## 3rd Qu.:8.620 3rd Qu.: -2.200 3rd Qu.:0.9636 3rd Qu.:1.0000
## Max. :9.210 Max. : 7.600 Max. :1.1348 Max. :2.0000
str() prikazuje:
summary() daje:
Zašto je to važno?
Ako je numerička varijabla učitana kao character,
analize neće raditi kako očekujemo.
Tidyverse pristup
glimpse(diamonds)
## Rows: 2,690
## Columns: 12
## $ Carat.Size <dbl> 0.30, 0.44, 0.31, 0.66, 0.47, 0.40, 0.36, 0.52, 0.53, 0.…
## $ Color <chr> "E", "E", "E", "K", "H", "G", "D", "H", "D", "F", "F", "…
## $ Clarity <chr> "VVS1", "VS2", "VVS1", "SI1", "VS2", "VS1", "VS2", "SI2"…
## $ Depth <dbl> 60.0, 61.9, 61.3, 62.8, 59.1, 62.0, 61.3, 61.7, 59.4, 61…
## $ Table <dbl> 59, 58, 58, 57, 64, 59, 57, 61, 59, 60, 59, 58, 62, 57, …
## $ Cut <chr> "Excellent", "Excellent", "Excellent", "Excellent", "Ver…
## $ Report <chr> "GIA", "GIA", "GIA", "GIA", "GIA", "GIA", "GIA", "GIA", …
## $ Price <dbl> 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1001, 10…
## $ Log.Price <dbl> 6.907755, 6.907755, 6.907755, 6.907755, 6.907755, 6.9077…
## $ Table.Depth <dbl> -1.0, -3.9, -3.3, -5.8, 4.9, -3.0, -4.3, -0.7, -0.4, -1.…
## $ Table.Depth.1 <dbl> 0.9833333, 0.9369951, 0.9461664, 0.9076433, 1.0829103, 0…
## $ Test <dbl> 0, 1, 0, 2, 2, 2, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 1, 2, 0,…
glimpse():
Prednost za velike skupove podataka.
Base R pristup
summary(is.na(flights))
## year month day dep_time
## Mode :logical Mode :logical Mode :logical Mode :logical
## FALSE:336776 FALSE:336776 FALSE:336776 FALSE:328521
## TRUE :8255
## sched_dep_time dep_delay arr_time sched_arr_time
## Mode :logical Mode :logical Mode :logical Mode :logical
## FALSE:336776 FALSE:328521 FALSE:328063 FALSE:336776
## TRUE :8255 TRUE :8713
## arr_delay carrier flight tailnum
## Mode :logical Mode :logical Mode :logical Mode :logical
## FALSE:327346 FALSE:336776 FALSE:336776 FALSE:334264
## TRUE :9430 TRUE :2512
## origin dest air_time distance
## Mode :logical Mode :logical Mode :logical Mode :logical
## FALSE:336776 FALSE:336776 FALSE:327346 FALSE:336776
## TRUE :9430
## hour minute time_hour
## Mode :logical Mode :logical Mode :logical
## FALSE:336776 FALSE:336776 FALSE:336776
##
is.na() vraća TRUE/FALSE matricusummary() vraća sažetak (u ovom slučaju, sažetak
prebrojavanja TRUE/FALSE)Dobivamo broj NA vrijednosti po varijabli.
Tidyverse pristup
flights %>%
summarise(across(everything(), ~sum(is.na(.))))
## # A tibble: 1 × 19
## year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
## <int> <int> <int> <int> <int> <int> <int> <int>
## 1 0 0 0 8255 0 8255 8713 0
## # ℹ 11 more variables: arr_delay <int>, carrier <int>, flight <int>,
## # tailnum <int>, origin <int>, dest <int>, air_time <int>, distance <int>,
## # hour <int>, minute <int>, time_hour <int>
Kako se gradi ova naredba?
Na prvi pogled izgleda složeno. Zato ćemo je rastaviti korak po korak.
Operator %>% zove se pipe
(cijev).
On znači:
“Uzmi objekt s lijeve strane i proslijedi ga kao prvi argument funkciji s desne strane.”
Dakle:
flights %>% summarise(...)
je isto što i:
summarise(flights, ...)
summarise() služi za izračun sažetih
statistika nad podacima.
Primjer:
flightse %>%
summarise(mean = mean(dep_tima, na.rm = TRUE))
Rezultat je jedan red (jedan sažetak).
across() znači:
“Primijeni istu funkciju na više stupaca.”
Primjer:
flights %>%
summarise(across(dep_time, mean))
Ali mi želimo primijeniti funkciju na sve stupce, zato koristimo:
everything()
što znači:
“Odaberi sve stupce.”
~sum(is.na(.)) - ovdje koristimo anonimnu
funkciju (lambda funkciju). Znak ~ znači:
“Definiram kratku funkciju.”
To je skraćeni zapis za:
function(x) sum(is.na(x))
A točka . znači:
“trenutni stupac koji se obrađuje.”
Dakle:
~sum(is.na(.))
znači:
“Za svaki stupac izračunaj koliko ima NA vrijednosti.”
Sada spojimo sve zajedno
flights %>%
summarise(across(everything(), ~sum(is.na(.))))
Čita se ovako:
flightseverything())sum(is.na(.)))Iako djeluje kompliciranije, korisnost ovog pristupa očituje se pri:
Evo primjera za ovo posljednje, provjera po grupama:
flights %>%
group_by(origin) %>%
summarise(across(everything(), ~sum(is.na(.))))
## # A tibble: 3 × 19
## origin year month day dep_time sched_dep_time dep_delay arr_time
## <chr> <int> <int> <int> <int> <int> <int> <int>
## 1 EWR 0 0 0 3239 0 3239 3390
## 2 JFK 0 0 0 1863 0 1863 1995
## 3 LGA 0 0 0 3153 0 3153 3328
## # ℹ 11 more variables: sched_arr_time <int>, arr_delay <int>, carrier <int>,
## # flight <int>, tailnum <int>, dest <int>, air_time <int>, distance <int>,
## # hour <int>, minute <int>, time_hour <int>
Sada dobivamo broj NA vrijednosti po polazištu.
Općenito, ovu naredbu možemo zapisati kao algoritam:
FOR svaki stupac u heart_rate:
izračunaj broj NA vrijednosti
VRATI jedan red sa svim tim brojevima
Čišćenje podataka je faza transformacije koja uključuje:
U stvarnim podacima nazivi stupaca često su:
"Carat Size")"Price", "CUT")"Price (€)")"Županija")"Price", "price_value",
"PriceUSD")Takvi nazivi otežavaju:
Zato u ETL fazi među prvim koracima radimo standardizaciju naziva.
Zašto je to važno?
Ovo:
diamonds$`Carat Size`
je puno nepraktičnije od ovoga:
diamonds$carat_size
Osim toga, u tidyverse pristupu razmaci u nazivima stvaraju dodatne komplikacije.
Ručna standardizacija (base R)
Možemo ručno promijeniti imena:
names(diamonds)
## [1] "Carat.Size" "Color" "Clarity" "Depth"
## [5] "Table" "Cut" "Report" "Price"
## [9] "Log.Price" "Table.Depth" "Table.Depth.1" "Test"
names(diamonds) <- tolower(names(diamonds))
names(diamonds)
## [1] "carat.size" "color" "clarity" "depth"
## [5] "table" "cut" "report" "price"
## [9] "log.price" "table.depth" "table.depth.1" "test"
Nedostatak:
Automatska standardizacija (preporučeno)
Najjednostavnije rješenje je paket janitor.
library(janitor)
diamonds <- janitor::clean_names(diamonds)
names(diamonds)
## [1] "carat_size" "color" "clarity" "depth"
## [5] "table" "cut" "report" "price"
## [9] "log_price" "table_depth" "table_depth_1" "test"
Što clean_names() radi?
_Primjer:
| Prije | Poslije |
|---|---|
| “Carat Size” | carat_size |
| “Price (€)” | price_eur |
| “Županija” | zupanija |
Preporučena ETL praksa
diamonds <- janitor::clean_names(diamonds)
Nakon toga dosljedno koristiti samo standardizirane nazive
U analitičkom izvještaju jasno navesti da je izvršena standardizacija
Mini-provjera razmaka
any(grepl(" ", names(diamonds)))
## [1] FALSE
Ako je rezultat FALSE, u nazivima nema razmaka koji bi
mogli stvarati probleme.
Zašto je ovo važno u profesionalnoj praksi?
U timskom radu:
Jednostavno isključivanje pomoću na.rm = TRUE
Ako samo želimo izračunati pokazatelje bez brisanja podataka:
mean(flights$dep_time, na.rm = TRUE)
## [1] 1349.11
median(flights$dep_time, na.rm = TRUE)
## [1] 1401
Što znači na.rm = TRUE?
ignorira NA vrijednosti
ne briše redove
primjenjuje se samo u toj funkciji
Ovo je najmanje invazivna metoda jer:
podaci ostaju neizmijenjeni
samo se NA ignorira u izračunu
Važno:
Ako radimo regresiju ili model, često će model automatski izbaciti retke s NA.
Korištenje which() za identifikaciju i uklanjanje po
broju retka
which() vraća indekse (brojeve redaka) gdje je uvjet
zadovoljen.
na_index <- which(is.na(flights$dep_time))
Sada znamo točno koji su redci problematični.
Uklanjanje:
flights_clean <- flights[-na_index, ]
str(flights_clean)
## tibble [328,521 × 19] (S3: tbl_df/tbl/data.frame)
## $ year : int [1:328521] 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 ...
## $ month : int [1:328521] 1 1 1 1 1 1 1 1 1 1 ...
## $ day : int [1:328521] 1 1 1 1 1 1 1 1 1 1 ...
## $ dep_time : int [1:328521] 517 533 542 544 554 554 555 557 557 558 ...
## $ sched_dep_time: int [1:328521] 515 529 540 545 600 558 600 600 600 600 ...
## $ dep_delay : num [1:328521] 2 4 2 -1 -6 -4 -5 -3 -3 -2 ...
## $ arr_time : int [1:328521] 830 850 923 1004 812 740 913 709 838 753 ...
## $ sched_arr_time: int [1:328521] 819 830 850 1022 837 728 854 723 846 745 ...
## $ arr_delay : num [1:328521] 11 20 33 -18 -25 12 19 -14 -8 8 ...
## $ carrier : chr [1:328521] "UA" "UA" "AA" "B6" ...
## $ flight : int [1:328521] 1545 1714 1141 725 461 1696 507 5708 79 301 ...
## $ tailnum : chr [1:328521] "N14228" "N24211" "N619AA" "N804JB" ...
## $ origin : chr [1:328521] "EWR" "LGA" "JFK" "JFK" ...
## $ dest : chr [1:328521] "IAH" "IAH" "MIA" "BQN" ...
## $ air_time : num [1:328521] 227 227 160 183 116 150 158 53 140 138 ...
## $ distance : num [1:328521] 1400 1416 1089 1576 762 ...
## $ hour : num [1:328521] 5 5 5 5 6 5 6 6 6 6 ...
## $ minute : num [1:328521] 15 29 40 45 0 58 0 0 0 0 ...
## $ time_hour : POSIXct[1:328521], format: "2013-01-01 05:00:00" "2013-01-01 05:00:00" ...
Što znači minus ispred indeksa?
Ovaj pristup je eksplicitan i često se koristi kad:
želimo manipulirati indeksima
želimo dodatno analizirati raspored NA po redoslijedu
Base R pristup
summary(is.na(flights))
## year month day dep_time
## Mode :logical Mode :logical Mode :logical Mode :logical
## FALSE:336776 FALSE:336776 FALSE:336776 FALSE:328521
## TRUE :8255
## sched_dep_time dep_delay arr_time sched_arr_time
## Mode :logical Mode :logical Mode :logical Mode :logical
## FALSE:336776 FALSE:328521 FALSE:328063 FALSE:336776
## TRUE :8255 TRUE :8713
## arr_delay carrier flight tailnum
## Mode :logical Mode :logical Mode :logical Mode :logical
## FALSE:327346 FALSE:336776 FALSE:336776 FALSE:334264
## TRUE :9430 TRUE :2512
## origin dest air_time distance
## Mode :logical Mode :logical Mode :logical Mode :logical
## FALSE:336776 FALSE:336776 FALSE:327346 FALSE:336776
## TRUE :9430
## hour minute time_hour
## Mode :logical Mode :logical Mode :logical
## FALSE:336776 FALSE:336776 FALSE:336776
##
flights$dep_delay[is.na(flights$dep_delay)] <- median(flights$dep_delay, na.rm = TRUE)
Što se događa?
na.rm = TRUE ignorira NA u računuZašto medijan?
Tidyverse pristup
flights <- flights %>%
mutate(dep_delay = ifelse(is.na(dep_delay),
median(dep_delay, na.rm = TRUE),
dep_delay))
Prednost:
Primjer:
x <- c("10", "12", "o5", "18")
as.numeric(x)
## Warning: NAs introduced by coercion
## [1] 10 12 NA 18
Što se događa?
Ispravak
x_clean <- gsub("o", "0", x)
as.numeric(x_clean)
## [1] 10 12 5 18
gsub() zamjenjuje znakove u stringu, u ovom slučaju
zamjenjuje slovo o s nulomnumeric.Transformacija znači:
diamonds$price <- as.numeric(diamonds$price)
str(diamonds)
## tibble [2,690 × 12] (S3: tbl_df/tbl/data.frame)
## $ carat_size : num [1:2690] 0.3 0.44 0.31 0.66 0.47 0.4 0.36 0.52 0.53 0.43 ...
## $ color : chr [1:2690] "E" "E" "E" "K" ...
## $ clarity : chr [1:2690] "VVS1" "VS2" "VVS1" "SI1" ...
## $ depth : num [1:2690] 60 61.9 61.3 62.8 59.1 62 61.3 61.7 59.4 61.5 ...
## $ table : num [1:2690] 59 58 58 57 64 59 57 61 59 60 ...
## $ cut : chr [1:2690] "Excellent" "Excellent" "Excellent" "Excellent" ...
## $ report : chr [1:2690] "GIA" "GIA" "GIA" "GIA" ...
## $ price : num [1:2690] 1000 1000 1000 1000 1000 ...
## $ log_price : num [1:2690] 6.91 6.91 6.91 6.91 6.91 ...
## $ table_depth : num [1:2690] -1 -3.9 -3.3 -5.8 4.9 -3 -4.3 -0.7 -0.4 -1.5 ...
## $ table_depth_1: num [1:2690] 0.983 0.937 0.946 0.908 1.083 ...
## $ test : num [1:2690] 0 1 0 2 2 2 0 0 0 2 ...
Napomena: U R-u postoje tri slične naredbe koje se često miješaju:
| Funkcija | Što radi? | Koristi se kad |
|---|---|---|
is.numeric() |
Provjerava je li objekt numerički | želimo testirati tip |
as.numeric() |
Pretvara objekt u numerički | želimo promijeniti tip |
numeric() |
Kreira prazan numerički vektor | želimo inicijalizirati objekt |
slične funkcije za druge tipove
is.character() / as.character()
is.factor() / as.factor()
is.Date() / as.Date()
is.integer() / as.integer()
Tidyverse pristup
diamonds <- diamonds %>%
mutate(price = as.numeric(price))
Prednost:
$diamonds$cut <- factor(diamonds$cut,
levels = c("Good", "Very Good", "Excellent", "Ideal"),
ordered = TRUE)
str(diamonds)
## 'data.frame': 2690 obs. of 12 variables:
## $ carat_size : num 0.3 0.44 0.31 0.66 0.47 0.4 0.36 0.52 0.53 0.43 ...
## $ color : chr "E" "E" "E" "K" ...
## $ clarity : chr "VVS1" "VS2" "VVS1" "SI1" ...
## $ depth : num 60 61.9 61.3 62.8 59.1 62 61.3 61.7 59.4 61.5 ...
## $ table : num 59 58 58 57 64 59 57 61 59 60 ...
## $ cut : Ord.factor w/ 4 levels "Good"<"Very Good"<..: 3 3 3 3 2 3 3 2 2 3 ...
## $ report : chr "GIA" "GIA" "GIA" "GIA" ...
## $ price : num 1000 1000 1000 1000 1000 ...
## $ log_price : num 6.91 6.91 6.91 6.91 6.91 ...
## $ table_depth : num -1 -3.9 -3.3 -5.8 4.9 -3 -4.3 -0.7 -0.4 -1.5 ...
## $ table_depth_1: num 0.983 0.937 0.946 0.908 1.083 ...
## $ test : num 0 1 0 2 2 2 0 0 0 2 ...
Zašto je ovo važno?
Ako ne definiramo redoslijed:
Cilj: dobiti informativniju mjeru.
diamonds$price_per_carat <- diamonds$price / diamonds$carat_size
str(diamonds)
## 'data.frame': 2690 obs. of 13 variables:
## $ carat_size : num 0.3 0.44 0.31 0.66 0.47 0.4 0.36 0.52 0.53 0.43 ...
## $ color : chr "E" "E" "E" "K" ...
## $ clarity : chr "VVS1" "VS2" "VVS1" "SI1" ...
## $ depth : num 60 61.9 61.3 62.8 59.1 62 61.3 61.7 59.4 61.5 ...
## $ table : num 59 58 58 57 64 59 57 61 59 60 ...
## $ cut : Ord.factor w/ 4 levels "Good"<"Very Good"<..: 3 3 3 3 2 3 3 2 2 3 ...
## $ report : chr "GIA" "GIA" "GIA" "GIA" ...
## $ price : num 1000 1000 1000 1000 1000 ...
## $ log_price : num 6.91 6.91 6.91 6.91 6.91 ...
## $ table_depth : num -1 -3.9 -3.3 -5.8 4.9 -3 -4.3 -0.7 -0.4 -1.5 ...
## $ table_depth_1 : num 0.983 0.937 0.946 0.908 1.083 ...
## $ test : num 0 1 0 2 2 2 0 0 0 2 ...
## $ price_per_carat: num 3333 2273 3226 1515 2128 ...
Tidyverse
diamonds <- diamonds %>%
mutate(price_per_carat = price / carat_size)
glimpse(diamonds)
## Rows: 2,690
## Columns: 13
## $ carat_size <dbl> 0.30, 0.44, 0.31, 0.66, 0.47, 0.40, 0.36, 0.52, 0.53, …
## $ color <chr> "E", "E", "E", "K", "H", "G", "D", "H", "D", "F", "F",…
## $ clarity <chr> "VVS1", "VS2", "VVS1", "SI1", "VS2", "VS1", "VS2", "SI…
## $ depth <dbl> 60.0, 61.9, 61.3, 62.8, 59.1, 62.0, 61.3, 61.7, 59.4, …
## $ table <dbl> 59, 58, 58, 57, 64, 59, 57, 61, 59, 60, 59, 58, 62, 57…
## $ cut <ord> Excellent, Excellent, Excellent, Excellent, Very Good,…
## $ report <chr> "GIA", "GIA", "GIA", "GIA", "GIA", "GIA", "GIA", "GIA"…
## $ price <dbl> 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1001, …
## $ log_price <dbl> 6.907755, 6.907755, 6.907755, 6.907755, 6.907755, 6.90…
## $ table_depth <dbl> -1.0, -3.9, -3.3, -5.8, 4.9, -3.0, -4.3, -0.7, -0.4, -…
## $ table_depth_1 <dbl> 0.9833333, 0.9369951, 0.9461664, 0.9076433, 1.0829103,…
## $ test <dbl> 0, 1, 0, 2, 2, 2, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 1, 2, …
## $ price_per_carat <dbl> 3333.333, 2272.727, 3225.806, 1515.152, 2127.660, 2500…
Zašto?
Date = datum bez vremena
POSIXct = datum+vrijeme, dobar za
logove/senzore
library(lubridate)
x <- c("2026-02-25", "2026-03-01")
x <- as.Date(x, format = "%Y-%m-%d")
str(x)
## Date[1:2], format: "2026-02-25" "2026-03-01"
class(x)
## [1] "Date"
y <- c("1jan1960", "2jan1960", "31mar1960", "30jul1960")
y <- as.Date(y, "%d%b%Y")
str(y)
## Date[1:4], format: NA NA NA NA
class(y)
## [1] "Date"
t <- c("2026-02-25 14:30:00", "2026-02-25 16:10:00")
t <- as.POSIXct(t, format = "%Y-%m-%d %H:%M:%OS")
str(t)
## POSIXct[1:2], format: "2026-02-25 14:30:00" "2026-02-25 16:10:00"
class(t)
## [1] "POSIXct" "POSIXt"
sum(duplicated(flights))
## [1] 0
flights_unique <- flights[!duplicated(flights), ]
i tidyverse alternativa:
flights_unique <- flights %>% distinct()
glimpse(flights_unique)
## Rows: 336,776
## Columns: 19
## $ year <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2…
## $ month <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ day <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
## $ dep_time <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, …
## $ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, …
## $ dep_delay <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1…
## $ arr_time <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849,…
## $ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851,…
## $ arr_delay <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -1…
## $ carrier <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "…
## $ flight <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 4…
## $ tailnum <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N394…
## $ origin <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA",…
## $ dest <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD",…
## $ air_time <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 1…
## $ distance <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, …
## $ hour <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6…
## $ minute <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0…
## $ time_hour <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013-01-01 0…
Provjeriti:
negativne vrijednosti (ima li ih gdje ne smiju biti)
raspone (npr. between())
summary(diamonds$price)
Tidyverse:
diamonds %>% filter(price < 0 | carat_size <= 0)
## [1] carat_size color clarity depth
## [5] table cut report price
## [9] log_price table_depth table_depth_1 test
## [13] price_per_carat
## <0 rows> (or 0-length row.names)
premium_diamonds <- diamonds[diamonds$cut == "Ideal", ]
str(premium_diamonds)
## 'data.frame': 185 obs. of 13 variables:
## $ carat_size : num 0.51 0.41 0.33 0.33 0.4 0.32 0.37 0.38 0.31 0.35 ...
## $ color : chr "I" "G" "D" "E" ...
## $ clarity : chr "SI1" "VS1" "VVS2" "VVS1" ...
## $ depth : num 61.7 61.4 60.7 61.4 61.7 61.8 61.4 61.5 61.9 61.6 ...
## $ table : num 55 55 57 57 57 55 57 56 56 57 ...
## $ cut : Ord.factor w/ 4 levels "Good"<"Very Good"<..: 4 4 4 4 4 4 4 4 4 4 ...
## $ report : chr "GIA" "GIA" "GIA" "GIA" ...
## $ price : num 1017 1018 1019 1036 1037 ...
## $ log_price : num 6.92 6.93 6.93 6.94 6.94 ...
## $ table_depth : num -6.7 -6.4 -3.7 -4.4 -4.7 -6.8 -4.4 -5.5 -5.9 -4.6 ...
## $ table_depth_1 : num 0.891 0.896 0.939 0.928 0.924 ...
## $ test : num 0 0 0 2 2 0 0 2 1 1 ...
## $ price_per_carat: num 1994 2483 3088 3139 2592 ...
Tidyverse
premium_diamonds <- diamonds %>%
filter(cut == "Ideal")
Prednost tidyverse:
large_diamonds <- diamonds[diamonds$carat_size > 2, ]
str(large_diamonds)
## 'data.frame': 2 obs. of 13 variables:
## $ carat_size : num 2.02 2.02
## $ color : chr "K" "J"
## $ clarity : chr "SI2" "SI2"
## $ depth : num 63.4 63.7
## $ table : num 56 58
## $ cut : Ord.factor w/ 4 levels "Good"<"Very Good"<..: 2 2
## $ report : chr "GIA" "GIA"
## $ price : num 9217 9984
## $ log_price : num 9.13 9.21
## $ table_depth : num -7.4 -5.7
## $ table_depth_1 : num 0.883 0.911
## $ test : num 0 0
## $ price_per_carat: num 4563 4943
Tidyverse
large_diamonds <- diamonds %>%
filter(carat_size > 2)
glimpse(large_diamonds)
## Rows: 2
## Columns: 13
## $ carat_size <dbl> 2.02, 2.02
## $ color <chr> "K", "J"
## $ clarity <chr> "SI2", "SI2"
## $ depth <dbl> 63.4, 63.7
## $ table <dbl> 56, 58
## $ cut <ord> Very Good, Very Good
## $ report <chr> "GIA", "GIA"
## $ price <dbl> 9217, 9984
## $ log_price <dbl> 9.128805, 9.208739
## $ table_depth <dbl> -7.4, -5.7
## $ table_depth_1 <dbl> 0.8832808, 0.9105180
## $ test <dbl> 0, 0
## $ price_per_carat <dbl> 4562.871, 4942.574
Na primjer, na nasumičan način odabiremo uzorak od 100 dijamanata.
Kako je u pitanju slučajni odabir, postavljamo seed kako
bismo ponovnim uzorkovanjem dobili isti uzorak.
set.seed(123)
sample_data <- diamonds[sample(nrow(diamonds), 100), ]
str(sample_data)
## 'data.frame': 100 obs. of 13 variables:
## $ carat_size : num 1.41 1.03 1.3 0.59 0.51 1.25 0.9 0.81 0.71 0.74 ...
## $ color : chr "H" "F" "F" "E" ...
## $ clarity : chr "SI1" "VS2" "SI2" "SI1" ...
## $ depth : num 61.7 62.3 60.4 62.6 60.2 62.8 63.8 60.1 61.1 62.2 ...
## $ table : num 62 56 58 59 60 61 58 59 60 58 ...
## $ cut : Ord.factor w/ 4 levels "Good"<"Very Good"<..: 2 3 2 3 3 2 1 3 3 3 ...
## $ report : chr "GIA" "GIA" "GIA" "GIA" ...
## $ price : num 7995 8229 6523 1591 1156 ...
## $ log_price : num 8.99 9.02 8.78 7.37 7.05 ...
## $ table_depth : num 0.3 -6.3 -2.4 -3.6 -0.2 -1.8 -5.8 -1.1 -1.1 -4.2 ...
## $ table_depth_1 : num 1.005 0.899 0.96 0.942 0.997 ...
## $ test : num 0 1 0 2 0 0 0 0 0 2 ...
## $ price_per_carat: num 5670 7989 5018 2697 2267 ...
Tidyverse
set.seed(123)
sample_data <- diamonds %>%
slice_sample(n = 100)
glimpse(sample_data)
## Rows: 100
## Columns: 13
## $ carat_size <dbl> 1.41, 1.03, 1.30, 0.59, 0.51, 1.25, 0.90, 0.81, 0.71, …
## $ color <chr> "H", "F", "F", "E", "F", "J", "G", "E", "E", "D", "F",…
## $ clarity <chr> "SI1", "VS2", "SI2", "SI1", "SI1", "VS2", "SI2", "SI1"…
## $ depth <dbl> 61.7, 62.3, 60.4, 62.6, 60.2, 62.8, 63.8, 60.1, 61.1, …
## $ table <dbl> 62, 56, 58, 59, 60, 61, 58, 59, 60, 58, 57, 59, 59, 61…
## $ cut <ord> Very Good, Excellent, Very Good, Excellent, Excellent,…
## $ report <chr> "GIA", "GIA", "GIA", "GIA", "GIA", "GIA", "GIA", "GIA"…
## $ price <dbl> 7995, 8229, 6523, 1591, 1156, 4950, 2877, 3167, 3339, …
## $ log_price <dbl> 8.986572, 9.015420, 8.783090, 7.372118, 7.052721, 8.50…
## $ table_depth <dbl> 0.3, -6.3, -2.4, -3.6, -0.2, -1.8, -5.8, -1.1, -1.1, -…
## $ table_depth_1 <dbl> 1.0048622, 0.8988764, 0.9602649, 0.9424920, 0.9966777,…
## $ test <dbl> 0, 1, 0, 2, 0, 0, 0, 0, 0, 2, 2, 1, 0, 1, 2, 1, 0, 2, …
## $ price_per_carat <dbl> 5670.213, 7989.320, 5017.692, 2696.610, 2266.667, 3960…
barplot(table(diamonds$cut))
Strukturni krug je popularan, ali neprimjeren za precizniju usporedbu.
cut_freq <- table(diamonds$cut)
pie(cut_freq,
main = "Distribucija reza (Cut)",
clockwise = TRUE)
table_cut_color <- table(diamonds$cut, diamonds$color)
barplot(table_cut_color, beside = TRUE)
tab_cut_color <- table(diamonds$cut, diamonds$color)
mosaicplot(tab_cut_color,
main = "Mosaic: Cut × Color",
las = 2)
Kako čitati mosaic:
širina stupca ~ ukupna frekvencija prve varijable
visine unutar stupca ~ udjeli druge varijable
promatramo uzorke ploha
hist(diamonds$price, breaks = 30)
boxplot(diamonds$price)
boxplot(price ~ cut, data = diamonds)
Jednostavna tablica prikazuje jednu varijablu za jedan statistički skup. Ako je u pitanju numerička varijabla, onda kažemo da su podaci grupirani, jer će u predstupcu biti razredi. Za kvalitativnu varijablu, u predstupcu će biti zapisani modaliteti. Prvi stupac se odnosi na frekvencije.
freq_price <- table(cut(diamonds$price, breaks = 10))
freq_price
##
## (991,1.9e+03] (1.9e+03,2.8e+03] (2.8e+03,3.7e+03] (3.7e+03,4.6e+03]
## 720 399 264 349
## (4.6e+03,5.5e+03] (5.5e+03,6.4e+03] (6.4e+03,7.3e+03] (7.3e+03,8.2e+03]
## 272 200 162 128
## (8.2e+03,9.1e+03] (9.1e+03,1e+04]
## 95 101
tabyl(diamonds$cut) # iz paketa janitor
## diamonds$cut n percent
## Good 165 0.06133829
## Very Good 1064 0.39553903
## Excellent 1276 0.47434944
## Ideal 185 0.06877323
Tablica kontingencije je sažeti prikaz frekvencija koje se dobivaju presjekom dvije varijable, mjerene za jedan statistički skup.
table(diamonds$cut, diamonds$color)
##
## D E F G H I J K
## Good 16 29 23 22 23 23 21 8
## Very Good 106 209 164 130 154 124 121 56
## Excellent 139 227 213 209 192 142 102 52
## Ideal 16 39 31 35 25 27 9 3
Skupna tablica predstavlja frekvencije modaliteta (ili razreda) jedne varijable mjerene za više statističkih skupova. Ako dijamante s različitim rezom promatramo zasebno, pri čemu je svaki tip reza zasebni statistički skup, tada ćemo tablicu kreirati na ovaj način:
diamonds$price_class <- cut(diamonds$price, breaks = 10)
head(diamonds$price_class)
## [1] (991,1.9e+03] (991,1.9e+03] (991,1.9e+03] (991,1.9e+03] (991,1.9e+03]
## [6] (991,1.9e+03]
## 10 Levels: (991,1.9e+03] (1.9e+03,2.8e+03] ... (9.1e+03,1e+04]
table(diamonds$price_class, diamonds$cut)
##
## Good Very Good Excellent Ideal
## (991,1.9e+03] 45 285 344 46
## (1.9e+03,2.8e+03] 16 153 205 25
## (2.8e+03,3.7e+03] 26 111 107 20
## (3.7e+03,4.6e+03] 22 179 132 16
## (4.6e+03,5.5e+03] 22 101 130 19
## (5.5e+03,6.4e+03] 16 74 95 15
## (6.4e+03,7.3e+03] 4 62 87 9
## (7.3e+03,8.2e+03] 6 42 74 6
## (8.2e+03,9.1e+03] 6 28 49 12
## (9.1e+03,1e+04] 2 29 53 17
sp <- describe(diamonds$price)
sp
## vars n mean sd median trimmed mad min max range skew
## X1 1 2690 3971.47 2420.23 3604 3724.7 2686.47 1000 10000 9000 0.68
## kurtosis se
## X1 -0.51 46.66
t(sp) # transponiramo za klasični prikaz
## X1
## vars 1.0000000
## n 2690.0000000
## mean 3971.4713755
## sd 2420.2341906
## median 3604.0000000
## trimmed 3724.6970260
## mad 2686.4712000
## min 1000.0000000
## max 10000.0000000
## range 9000.0000000
## skew 0.6849823
## kurtosis -0.5088668
## se 46.6639236
U ETL procesu, faza Load znači da “pripremljene” podatke i rezultate:
spremamo u datoteke (CSV, Excel, RDS…)
spremamo u bazu podataka
spremamo grafove kao slike
spremamo tablice kao izvještajne artefakte (CSV/HTML)
Cilj je da rezultat bude ponovljiv i prenosiv (npr. za kolege, za kasniji rad, za izvještaj).
Preporuka: struktura mapa u projektu
dir.create("data_raw", showWarnings = FALSE)
dir.create("data_clean", showWarnings = FALSE)
dir.create("outputs", showWarnings = FALSE)
dir.create("outputs/figures", showWarnings = FALSE)
dir.create("outputs/tables", showWarnings = FALSE)
Pohrana u csv
readr::write_csv(diamonds, "data_clean/diamonds_clean.csv") # datoteka data_clean, dokument diamonds_clean.csv
Pohrana u xlsx
library(openxlsx)
write.xlsx(diamonds, "data_clean/diamonds_clean.xlsx")
Pohrana u RDS
RDS čuva objekt “kako jest” (tipove varijabli, faktore, datume). Najbolja opcija za ponovno korištenje u R-u.
saveRDS(diamonds, "data_clean/diamonds_clean.rds")
Pohrana u SQLite bazu
con <- dbConnect(RSQLite::SQLite(), "data_clean/etl_example.sqlite")
dbWriteTable(con, "diamonds_clean", diamonds, overwrite = TRUE)
dbListTables(con)
dbDisconnect(con)
U R-u graf možemo spremiti tako da:
otvorimo “uređaj” (npr. PNG)
nacrtamo graf
zatvorimo uređaj (dev.off())
Base R graf → PNG
png("outputs/figures/hist_price.png", width = 900, height = 600)
hist(diamonds$price, breaks = 30, main = "Histogram cijena", xlab = "Price")
dev.off()
ggplot graf → PNG
# install.packages("ggplot2")
library(ggplot2)
p <- ggplot(diamonds, aes(x = Price)) + geom_histogram(bins = 30)
ggsave("outputs/figures/gg_hist_price.png", plot = p, width = 8, height = 5, dpi = 150)
PDF (dobro za izvještaje i tisak)
pdf("outputs/figures/boxplot_price_by_cut.pdf", width = 8, height = 5)
boxplot(price ~ cut, data = diamonds, main = "Price ~ Cut", xlab = "Cut", ylab = "Price")
dev.off()
Tablica frekvencija → CSV
Primjer: frekvencije razreda cijena.
freq_price <- table(cut(diamonds$price, breaks = 10))
freq_price
##
## (991,1.9e+03] (1.9e+03,2.8e+03] (2.8e+03,3.7e+03] (3.7e+03,4.6e+03]
## 720 399 264 349
## (4.6e+03,5.5e+03] (5.5e+03,6.4e+03] (6.4e+03,7.3e+03] (7.3e+03,8.2e+03]
## 272 200 162 128
## (8.2e+03,9.1e+03] (9.1e+03,1e+04]
## 95 101
Pretvorba u data frame i spremanje:
freq_price_df <- as.data.frame(freq_price)
names(freq_price_df) <- c("Price_class", "Frequency")
readr::write_csv(freq_price_df, "outputs/tables/freq_price.csv")
Kontingencijska tablica (Cut × Color) → CSV
tab_cut_color <- table(diamonds$cut, diamonds$color)
tab_cut_color
Spremanje:
tab_df <- as.data.frame(tab_cut_color)
names(tab_df) <- c("Cut", "Color", "Frequency")
readr::write_csv(tab_df, "outputs/tables/cut_by_color.csv")
Tablica statističkih pokazatelja → CSV
sp <- psych::describe(diamonds$price)
sp_df <- as.data.frame(t(sp))
readr::write_csv(sp_df, "outputs/tables/describe_price.csv")
sp_df
Tablica u HTML (za web/izvještaj)
library(knitr)
knitr::kable(freq_price_df, caption = "Frekvencijska tablica: Price (razredi)")