Sada je vreme da naučimo nešto o data wrangling
-u, veštini dobijanja podataka u korisnom obliku za vizuelizaciju i modelovanje. Wrangling je veoma važan, bez toga ne možemo raditi sa svojim podacima! Postoje tri glavna dela wrangling-a:
Kao što smo već pomenuli, tiblovi su baze podataka, ali podešene malo drugačije od običnih, kako bi nam rad bio lakši. R je star jezik, a neke stvari koje su bile korisne pre 10 ili 20 godina, sada otežavaju rad. Komplikovano je promeniti bazu R-a, pa se većina inovacija pojavljuje u paketima. Ovde ćemo opisati tibble
paket. Ako želite da saznate nešto više o tiblovima, kucajte vignette("tibble")
.
Kada smo radili sa dplyr
paketom koristili smo tiblove, a koristićemo ih i sada. Međutim, većina drugih paketa koristi obične baze, ali to nije problem jer lako možemo konvertovati običnu bazu u tibl, koristeći as.tibble()
.
library(readr)
as_tibble(iris)
## # A tibble: 150 x 5
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## <dbl> <dbl> <dbl> <dbl> <fct>
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
## 7 4.6 3.4 1.4 0.3 setosa
## 8 5 3.4 1.5 0.2 setosa
## 9 4.4 2.9 1.4 0.2 setosa
## 10 4.9 3.1 1.5 0.1 setosa
## # ... with 140 more rows
Možemo kreirati novi tibl iz pojedinačnih vektora sa tibble()
. Ova funkcija će automatski produžiti ulaz dužine 1, a dozvoljava i da pozivamo promenljivu koju smo upravo kreirali:
tibble(
x = 1:5,
y = 1,
z = x ^ 2 + y
)
## # A tibble: 5 x 3
## x y z
## <int> <dbl> <dbl>
## 1 1 1 2
## 2 2 1 5
## 3 3 1 10
## 4 4 1 17
## 5 5 1 26
Moguće je da tibl ima imena kolona koje nisu validna imena promenljivih u R-u, tj. ne-sintaksna imena. Na primer, možda ne počinju slovom, ili možda sadrže neobične karaktere kao što je razmak. Da bismo pozvali ove promenljive, moramo ih okružiti sa simbolom `
.
tb = tibble(
`:)` = "smile",
` ` = "space",
`2000` = "number"
)
tb
## # A tibble: 1 x 3
## `:)` ` ` `2000`
## <chr> <chr> <chr>
## 1 smile space number
Takođe, trebaće nam ovaj simbol kad radimo sa ovim promenljivama u drugim paketima, poput ggplot2
, dplyr
i tidyr
.
Drugi način za kreiranje tiblova je tribble()
, skraćenica za transponovani tibl. Funkcija tribble()
radi na sledeći način: zaglavlja kolona su definisana formulama (tj. počinju sa ~), a unosi su razdvojeni zarezima. Ovo omogućava da se prikaže mala količina podataka u lako čitljivom obliku. Korisno je dodati komentar da bismo bili potpuno sigurni gde je zaglavlje.
tribble(
~x, ~y, ~z,
#--|--|----
"a", 2, 3.6,
"b", 1, 8.5
)
## # A tibble: 2 x 3
## x y z
## <chr> <dbl> <dbl>
## 1 a 2 3.6
## 2 b 1 8.5
Dve glavne razlike u korišćenju tibla u odnosu na klasični data.frame
su prikazivanje i izdvajanje podskupova podataka.
Tiblovi imaju drugačiji način prikazivanja - prikazuje se samo prvih 10 redova i sve kolone koje staju na ekran. Ovo puno olakšava rad sa bazama koje sadrže veliku količinu podataka. Pored imena, za svaku kolonu se prikazuje i tip, lepa karakteristika pozajmljenja iz str()
.
library(lubridate)
tibble(
a = now() + runif(1000) * 86400,
b = today() + runif(1000) * 30,
c = 1:1000,
d = runif(1000),
e = sample(letters, 1000, replace = TRUE)
)
## # A tibble: 1,000 x 5
## a b c d e
## <dttm> <date> <int> <dbl> <chr>
## 1 2019-04-07 10:46:54 2019-04-27 1 0.519 m
## 2 2019-04-07 01:30:29 2019-04-28 2 0.162 l
## 3 2019-04-07 19:09:50 2019-04-15 3 0.545 d
## 4 2019-04-07 11:40:49 2019-04-18 4 0.486 x
## 5 2019-04-07 10:45:31 2019-05-05 5 0.350 f
## 6 2019-04-06 22:24:25 2019-04-26 6 0.921 z
## 7 2019-04-07 20:49:17 2019-04-08 7 0.235 o
## 8 2019-04-07 02:58:12 2019-05-05 8 0.693 l
## 9 2019-04-07 21:41:56 2019-04-29 9 0.901 v
## 10 2019-04-07 10:53:33 2019-04-17 10 0.351 s
## # ... with 990 more rows
Tiblovi su napravljeni tako da ne preplavimo konzolu kada prikazujemo veliku bazu podataka. Međutim, ponekad nam treba više nego što nam pruža podrazumevani prikaz. Postoji nekoliko opcija koje nam mogu pomoći.
Prvo, možemo primeniti print()
na bazu i kontrolisati broj redova (n
) koji želimo da prikažemo (width = Inf
prikazuje sve kolone).
nycflights13::flights %>%
print(n = 10, width = Inf)
## # A tibble: 336,776 x 19
## year month day dep_time sched_dep_time dep_delay arr_time
## <int> <int> <int> <int> <int> <dbl> <int>
## 1 2013 1 1 517 515 2 830
## 2 2013 1 1 533 529 4 850
## 3 2013 1 1 542 540 2 923
## 4 2013 1 1 544 545 -1 1004
## 5 2013 1 1 554 600 -6 812
## 6 2013 1 1 554 558 -4 740
## 7 2013 1 1 555 600 -5 913
## 8 2013 1 1 557 600 -3 709
## 9 2013 1 1 557 600 -3 838
## 10 2013 1 1 558 600 -2 753
## sched_arr_time arr_delay carrier flight tailnum origin dest air_time
## <int> <dbl> <chr> <int> <chr> <chr> <chr> <dbl>
## 1 819 11 UA 1545 N14228 EWR IAH 227
## 2 830 20 UA 1714 N24211 LGA IAH 227
## 3 850 33 AA 1141 N619AA JFK MIA 160
## 4 1022 -18 B6 725 N804JB JFK BQN 183
## 5 837 -25 DL 461 N668DN LGA ATL 116
## 6 728 12 UA 1696 N39463 EWR ORD 150
## 7 854 19 B6 507 N516JB EWR FLL 158
## 8 723 -14 EV 5708 N829AS LGA IAD 53
## 9 846 -8 B6 79 N593JB JFK MCO 140
## 10 745 8 AA 301 N3ALAA LGA ORD 138
## distance hour minute time_hour
## <dbl> <dbl> <dbl> <dttm>
## 1 1400 5 15 2013-01-01 05:00:00
## 2 1416 5 29 2013-01-01 05:00:00
## 3 1089 5 40 2013-01-01 05:00:00
## 4 1576 5 45 2013-01-01 05:00:00
## 5 762 6 0 2013-01-01 06:00:00
## 6 719 5 58 2013-01-01 05:00:00
## 7 1065 6 0 2013-01-01 06:00:00
## 8 229 6 0 2013-01-01 06:00:00
## 9 944 6 0 2013-01-01 06:00:00
## 10 733 6 0 2013-01-01 06:00:00
## # ... with 3.368e+05 more rows
Takođe, možemo podesiti podrazumevano ponašanje podešavanjem opcija:
options(tibble.print_max = n, tibble.print_min = m)
: ako ima više od n redova, prikazuje samo m redova. Možemo koristiti options(tibble.print_min = Inf)
da bismo uvek ispisivali sve redove.
options(tibble.width = Inf)
uvek prikazuje sve kolone, bez obzira na širinu ekrana.
Kompletnu listu možemo videti pozivajući package?tibble
.
Poslednja opcija je, naravno, korišćenje ugrađenog RStudio
“pregledača” podataka da bismo dobili prikaz kompletne baze. Ovo je uvek korisno na kraju dugog lanca manipulisanja podacima.
nycflights13::flights %>%
View()
Ako želimo da izdvojimo jednu promenljivu, trebaće nam $
ili [[
. Korišćenjem [[
se može izdvojiti po imenu ili poziciji, $
samo izdvaja po imenu, ali je malo lakše za kucanje.
df = tibble(
x = runif(5),
y = rnorm(5)
)
#izdvajamo po imenu
df$x
## [1] 0.2297936 0.7512522 0.2833471 0.2409461 0.8931826
#ili
df[["x"]]
## [1] 0.2297936 0.7512522 0.2833471 0.2409461 0.8931826
#izdvajamo po poziciji
df[[1]]
## [1] 0.2297936 0.7512522 0.2833471 0.2409461 0.8931826
Da bismo koristili cevi, moramo da ubacimo i specijalni “placeholder” .
.
df %>% .$x
## [1] 0.2297936 0.7512522 0.2833471 0.2409461 0.8931826
df %>% .[["x"]]
## [1] 0.2297936 0.7512522 0.2833471 0.2409461 0.8931826
U poređenju sa data.frame
-ovima, tiblovi su stroži: uvek pružaju upozorenje ako kolona kojoj pokušavamo da pristupimo ne postoji.
Neke funkcije koje su radile za data.frame
-ove, ne rade za tiblove. Ako naiđemo na takvu, možemo jednostavno da koristimo as.data.frame()
da se vratimo na data.frame
.
class(as.data.frame(tb))
## [1] "data.frame"
Podaci ugrađeni u R su nam jako korisni ako želimo da naučimo kako da radimo sa podacima, međutim, u nekom trenutku želimo da počnemo da radimo i sa sopstvenim podacima. Sada ćemo naučiti kako da učitamo obične tekstualne datoteke u R-u. Ovde ćemo samo ogrebati načine uvoza podataka, ali mnogi od principa se prenose i na druge oblike podataka.
Većina readr
funkcija bavi se pretvaranjem datoteka u baze podataka:
read_csv()
čita datoteke gde se za razdvajanje koristi zarez, read_csv2()
čita datoteke razdvojene tačkom i zarezom (uobičajeno za zapise gde se zarezom odvaja decimalni zapis), read_tsv()
čita datoteke razdvojene tab
-om, a read_delim()
čita datoteke razdvojene bilo kojim delimiterom.
read_fwf()
čita datoteke fiksne širine. Možemo da specifikujemo polja njihovim širinama fwf_widhts()
ili njihovom pozicijom fwf_positions()
. Funkcija read_table()
čita uobičajen tip datoteka fiksne širine gde su kolone razdvojene praznim prostorom.
Sve ove funkcije imaju istu sintaksu: kada ovladamo jednom, možemo koristiti druge sa lakoćom. Za ostatak ćemo se fokusirati na read_csv()
. Ne samo da su csv fajlovi jedan od najčešćih oblika skladištenja podataka, već kada shvatimo read_csv()
, možemo lako primeniti svoje znanje na sve druge funkcije u readr-u.
Prvi argument za read_csv()
je najvažniji: to je putanja do datoteke za čitanje.
heights = read_csv("heights.csv")
## Parsed with column specification:
## cols(
## earn = col_double(),
## height = col_double(),
## sex = col_character(),
## ed = col_integer(),
## age = col_integer(),
## race = col_character()
## )
Kada pokrenemo read_csv()
, štampa se ime i tip svake kolone. To je važan deo, kome ćemo se vratiti kasnije.
Takođe, možemo isporučiti csv fajl koji smo upravo kreirali. Ovo je korisno za eksperimentisanje sa readr
-om i uviđanje osnovnih pravila.
read_csv("a,b,c
1,2,3
4,5,6")
## # A tibble: 2 x 3
## a b c
## <int> <int> <int>
## 1 1 2 3
## 2 4 5 6
U oba slučaja read_csv()
koristi prvu liniju podataka za imena kolona, što je vrlo uobičajena konvencija. Postoje dva slučaja u kojima želimo da izmenimo ovo ponašanje:
skip = n
da preskočimo prvih n redova, ili upotrebiti comment = "#"
da preskočimo sve linije koje počinju sa (npr.) #
.read_csv("The first line of metadata
The second line of metadata
x,y,z
1,2,3", skip = 2)
## # A tibble: 1 x 3
## x y z
## <int> <int> <int>
## 1 1 2 3
read_csv("# A comment I want to skip
x,y,z
1,2,3", comment = "#")
## # A tibble: 1 x 3
## x y z
## <int> <int> <int>
## 1 1 2 3
col_names = FALSE
da bismo funkciji read_csv()
naglasili da ne tretira prvi red kao imena kolona, i ona će ih automatski obeležiti sa \(x_1,..,x_n\).read_csv("1,2,3\n4,5,6", col_names = FALSE)
## # A tibble: 2 x 3
## X1 X2 X3
## <int> <int> <int>
## 1 1 2 3
## 2 4 5 6
(\n
je prečica za dodavanje novog reda.)
Dodatno, možemo proslediti col_names
vektor, koji će se koristiti za imena kolona.
read_csv("1,2,3\n4,5,6", col_names = c("x", "y", "z"))
## # A tibble: 2 x 3
## x y z
## <int> <int> <int>
## 1 1 2 3
## 2 4 5 6
Druga opcija koja obično zahteva podešavanje je na
: ovo određuje vrednost (ili vrednosti) koje se koriste za predstavljanje nedostajućih vrednosti u našoj datoteci:
read_csv("a,b,c\n1,2,.", na = ".")
## # A tibble: 1 x 3
## a b c
## <int> <int> <chr>
## 1 1 2 <NA>
Ovo je sve što treba da znamo da bismo učitali ~75% CSV datoteka sa kojima se susrećemo u praksi. Međutim, da bismo učitali komplikovanije datoteke, moramo naučiti više o tome kako readr
analizira svaku kolonu, pretvarajući ih u R vektore.
Ranije smo, pri učitavanju datoteka, korsitili ugrađenu read.csv()
funkciju. Međutim, postoji nekoliko dobrih razloga za favorizovanje readr
funkcija nad baznim ekvivalentima:
Obično su mnogo brže (~10x) od svojih baznih ekvivalenata.
One prave tiblove, ne pretvaraju vektore karaktera u faktore, ne daju imena redovima, ili ubacuju imena kolona. To su najčešće neprilike u radu sa osnovnim R funkcijama.
Osnovne R funkcije nasleđuju neko ponašanje od operativnih sistema i okruženja, tako da kod koji radi na jednom računaru, možda neće raditi na nekom drugom.
Pozabavimo se sada malo parse_*()
funkcijama. Ove funkcije uzimaju vektor karaktera i vraćaju vektor specifičnijeg tipa (npr. logički, celobrojni, datumi, itd.):
str(parse_logical(c("TRUE", "FALSE", "NA")))
## logi [1:3] TRUE FALSE NA
str(parse_integer(c("1", "2", "3")))
## int [1:3] 1 2 3
str(parse_date(c("2010-01-01", "1979-10-14")))
## Date[1:2], format: "2010-01-01" "1979-10-14"
Ove funkcije su korisne same po sebi, ali su takođe vaĹžan deo za izgradnju readr-a
. Kada saznamo kako pojedinačni parseri rade u ovom odeljku, vratićemo se nazad i videti kako se oni uklapaju u parsiranje kompletne datoteke.
Funkcije parse_*()
tipa su uniformisane: prvi argument je vektor karaktera koji treba raščlaniti, a argument na
navodi koje stringove treba tretirati kao nedostajuće:
parse_integer(c("1", "231", ".", "456"), na = ".")
## [1] 1 231 NA 456
Ako parsiranje ne uspe, dobićemo upozorenje:
x = parse_integer(c("123", "345", "abc", "123.45"))
## Warning in rbind(names(probs), probs_f): number of columns of result is not
## a multiple of vector length (arg 1)
## Warning: 2 parsing failures.
## row # A tibble: 2 x 4 col row col expected actual expected <int> <int> <chr> <chr> actual 1 3 NA an integer abc row 2 4 NA no trailing characters .45
A neuspesi će nedostajati na izlazu:
x
## [1] 123 345 NA NA
## attr(,"problems")
## # A tibble: 2 x 4
## row col expected actual
## <int> <int> <chr> <chr>
## 1 3 NA an integer abc
## 2 4 NA no trailing characters .45
Ako postoji mnogo neuspešnih parsiranja, možemo da koristimo problems()
da dobijemo kompletan skup neuspeha. Ovo vraća tibl, kojim onda možemo manipulisati uz pomoć dplyr-a
.
problems(x)
## # A tibble: 2 x 4
## row col expected actual
## <int> <int> <chr> <chr>
## 1 3 NA an integer abc
## 2 4 NA no trailing characters .45
Korišćenje parsera je uglavnom pitanje razumevanja dostupnih podataka i toga kako se nositi sa različitim tipovima ulaza. Postoji osam posebno važnih parsera:
parse_logical()
i parse_integer()
raščlanjuju logičke i celobrojne vektore. U principu nema ničega što bi moglo poći po zlu pri korišćenju ovih parsera, pa ih nećemo dalje opisivati.
parse_double()
je strogo numerički parser, a parse_number()
je fleksibilniji numerički parser. Oni mogu biti komplikovaniji nego što nam se čini, jer se u različitim delovima sveta brojevi pišu na različite načine.
parse_character()
izgleda tako jednostavnom da se čini da nam nije ni potrebna. Međutim, jedna komplikacija ga može učiniti veoma važnim: kodiranje znakova.
parse_factor()
kreira faktore, strukturu podataka koju R koristi za predstavljanje kategoričkih promenljivih sa fiksnim i poznatim vrednostima.
parse_datetime()
, parse_date()
i parse_time()
omogućuju da parsiramo različite datume i vremena. One su najsloženije jer postoji mnogo različitih načina pisanja datuma.
Sledeći odeljci detaljnije opisuju ove parsere:
Deluje da je lako analizirati broj, ali ipak, mogu se javiti tri problema:
Ljudi pišu brojeve različito u različitim delovima sveta. Na primer, neke zemlje koriste tačku između celog i razlomljenog dela broja, dok druge koriste zarez.
Brojevi su često okruženi drugim znakovima i imaju neki drugi kontekst, kao npr. “$1000” ili “10%”.
Brojevi često sadrže znakove grupisanja cifara, kako bi ih učinili lakšim za čitanje, poput “1,000,000”, a ovi znakovi grupisanja se razlikuju širom sveta.
Da bi rešio prvi problem, readr
ima locale
, objekat koji određuje opcije parsiranja koje se razlikuju od mesta do mesta. Kada raščlanjujemo brojeve, najvažnija opcija koju podešavamo je karakter koji koristimo za decimalni znak. Možemo poništiti podrazumevanu vrednost za .
kreiranjem novog locale
i postavljanjem argumenta decimal_mark
:
parse_double("1.23")
## [1] 1.23
parse_double("1,23", locale = locale(decimal_mark = ","))
## [1] 1.23
Podrazumevani locale
readr
-a je prilagođen SAD-u, jer je generalno R usredsređen na SAD (tj. dokumentacija je pisana na američkom engleskom jeziku). Alternativni pristup bi bio da pokušamo da pogodimo podrazumevane vrednosti iz našeg operativnog sistema. Ovo je teško dobro uraditi, i, što je još važnije, čini naš kod slabim: čak iako radi na našem računaru, možda neće raditi kada ga, na primer, pošaljemo mejlom kolegi u drugoj zemlji.
parse_number()
rešava drugi problem: zanemaruje ne-numeričke znakove pre i posle broja. Ovo je posebno korisno za valute i procente, ali i radi izdvajanja brojeva ugrađenih u tekst.
parse_number("$100")
## [1] 100
parse_number("20%")
## [1] 20
parse_number("It costs $123.45")
## [1] 123.45
Poslednji problem se rešava kombinacijom parse_number()
i locale
: tada će parse_number()
ignorisati “oznaku grupisanja”.
# Koristi se u Americi
parse_number("$123,456,789")
## [1] 123456789
# Koristi se u mnogim delovima Evrope
parse_number("123.456.789", locale = locale(grouping_mark = "."))
## [1] 123456789
# Koristi se u Svajcarskoj
parse_number("123'456'789", locale = locale(grouping_mark = "'"))
## [1] 123456789
Vratimo se na gore spomenuti parce_character()
. Deluje nam da sve treba da bude veoma lako - funkcija treba samo da ispiše svoj unos. Nažalost, nije baš tako jednostavno, jer postoji više načina za predstavljanje istog stringa. Da bismo razumeli o čemu se radi, moramo uroniti malo u detalje o tome kako računari predstavljaju stringove. U R-u, možemo dobiti osnovnu reprezentaciju koristeći charToRaw()
:
charToRaw("Hadley")
## [1] 48 61 64 6c 65 79
Svaki heksadecimalni broj predstavlja bajt informacija: 48 je H, 61 je a, itd. U ovom slučaju, za kodiranje heksadecimalnih brojeva u karaktere, koristi se ASCII kodiranje. ASCII odlično radi sa predstavljanjem engleskih znakova, jer je to Američki stnadardni kod za razmenu informacija (ASCII).
Stvari postaju komplikovanije za druge jezike. Na početku, bilo je mnogo standarda za kodiranje neengleskih znakova, i da bismo ispravno interpretirali string koji nam je potreban, trebalo je da znamo i vrednosti i kodiranje. Na primer, dva često korišćena kodiranja su Latin1 (ili ISO-8859-1, koji se koristi za zapadnoevropske jezike) i Latin2 (ili ISO-8859-2, koji se koristi za istočnoevropske jezike). U Latin1, bajt b1
je “\(\pm\)”, ali u Latin2, to je “\(a\)”. Srećom, danas postoji standard koji je podržan skoro svuda: UTF-8. UTF-8 može da kodira skoro svaki karakter koji se danas koristi, kao i mnoge dodatne simbole (npr. emotikone).
readr
svuda koristi UTF-8: pretpostavlja da su naši podaci UTF-8 kodirani kad ih čita, i uvek koristi UTF-8 prilikom pisanja. Ovo je dobar standard, ali neće uspeti za podatke proizvedene u starijim sistemima koji ne razumeju UTF-8. Ako se to dogodi, stringovi će izgledati čudno kada ih prikažemo. Ponekad samo jedan ili dva znaka mogu biti neispravna, a ponekad ćemo dobiti potpune besmislice. Na primer:
x1 = "El Ni\xf1o was particularly bad this year"
x2 = "\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd"
x1
#> [1] "El Ni\xf1o was particularly bad this year"
x2
#> [1] "\x82\xb1\x82\xf1\x82??\xbf\x82\xcd"
Da bismo rešili ovaj problem, moramo precizirati kodiranje u parse_character()
:
parse_character(x1, locale = locale(encoding = "Latin1"))
#> [1] "El Nino was particularly bad this year"
parse_character(x2, locale = locale(encoding = "Shift-JIS"))
#> [1] "???????????????"
Kako da pronađemo odgovarajuće kodiranje? Ako budemo imali sreće, taj podatak će biti uključen negde u dokumentaciji o podacima. Nažalost, to se retko dešava, pa readr
obezbeđuje guess_encoding()
, koji nam pomaže da pogodimo kodiranje. Ovo nije sigurno tačno, i radi bolje kad imamo puno teksta (što ovde nije slučaj), ali je zgodno za početak. Obično ćemo morati da probamo nekoliko tipova kodiranja pre nego što pronađemo odgovarajuće.
guess_encoding(charToRaw(x1))
#> # A tibble: 2 x 2
#> encoding confidence
#> <chr> <dbl>
#> 1 ISO-8859-1 0.46
#> 2 ISO-8859-9 0.23
guess_encoding(charToRaw(x2))
#> # A tibble: 1 x 2
#> encoding confidence
#> <chr> <dbl>
#> 1 KOI8-R 0.42
Prvi argument u guess_encoding()
može biti ili putanja do datoteke ili, kao u ovom slučaju, “raw” vektor (što je korisno ako su stringovi već u R-u).
Kodiranje je, ipak, previše bogata i složena tema, a mi samo smo malo zagrebali površinu.
R koristi faktore za predstavljanje kategoričkih promenljivih koje imaju poznati skup mogućih vrednosti. Funkciji parse_factor()
prosleđujemo vektor poznatih nivoa (ona će generisati upozorenje svaki put kada se pojavi neočekivana vrednost).
fruit = c("apple", "banana")
parse_factor(c("apple", "banana", "bananana"), levels = fruit)
## Warning: 1 parsing failure.
## row # A tibble: 1 x 4 col row col expected actual expected <int> <int> <chr> <chr> actual 1 3 NA value in level set bananana
## [1] apple banana <NA>
## attr(,"problems")
## # A tibble: 1 x 4
## row col expected actual
## <int> <int> <chr> <chr>
## 1 3 NA value in level set bananana
## Levels: apple banana
Međutim, ako imamo mnogo problematičnih unosa, često je lakše ostaviti ih kao vektore karaktera, a zatim iskoristiti neke alate (o kojima ćemo saznati u delu o faktorima i stringovima) da bismo ih očistili.
Možemo birati između tri parsera u zavisnosti od toga da li želimo objekat tipa date
(računa se kao broj dana od 01.01.1970., sa negativnim vrednostima za ranije datume), date-time
(računa se kao broj sekundi od ponoći 01.01.1970.) ili time
(broj sekundi od ponoći).
Kada se pozove bez dodatnih argumenata:
parse_datetime()
očekuje ISO8601 datum-vreme. ISO8601 je međunarodni standard u kome su komponente datuma organizovane od najveće do najmanje: godina, mesec, dan, sat, minut, sekunda.parse_datetime("2010-10-01T2010")
## [1] "2010-10-01 20:10:00 UTC"
# Ako je vreme izostavljeno, bice postavljeno na ponoc
parse_datetime("20101010")
## [1] "2010-10-10 UTC"
parse_date()
očekuje četvorocifrenu godinu, zatim -
ili /
, mesec, -
ili /
, a onda dan:parse_date("2010-10-01")
## [1] "2010-10-01"
parse_time()
očekuje sate, zatim :
, minute, opciono :
i sekunde, i opciono am/pm
.library(hms)
parse_time("01:10 am")
## 01:10:00
parse_time("20:10:01")
## 20:10:01
Bazni R nema dobru ugrađenu klasu za podatke o vremenu, tako da mi koristimo onu koja je data u hms
paketu.
Ako ove postavke ne rade za naše podatke, možemo dodati svoj vlastiti format datuma i vremena, sastavljen od sledećih delova:
Godina
%Y
(4 cifre)
%y
(2 cifre); 00-69 = 2000-2069, 70-99 = 1970-1999.
Mesec
%m
(2 cifre)
%b
(skraćeno ime: npr. “Jan”)
%B
(celo ime: “Januar”)
Dan
%d
(2 cifre)
Vreme
%H
0-23 sati
%I
0-12, mora se koristiti sa %p
%p
AM/PM indikator
%M
minuti
%S
sekunde (celi brojevi)
%OS
sekunde (realni brojevi)
%Z
Vremenska zona (ime, kao npr. Amerika, Čikago). Treba biti oprezan sa skraćenicama (npr. “EST” predstavlja kanadsku vremensku zonu koja nema letnje računanje vremena, a ne standardno istočno vreme).
%z
Odstupanje od UTC, npr. +0800
Ne-cifre
%.
preskače jedan karakter koji nije cifra.
$%^{*}$
preskače bilo koji broj ne-cifara.
Najbolji način da otkrijemo ispravan format je kreiranje nekoliko primera u vektoru karaktera i testiranje jednom od funkcija parsiranja. Na primer:
parse_date("01/02/15", "%m/%d/%y")
## [1] "2015-01-02"
parse_date("01/02/15", "%d/%m/%y")
## [1] "2015-02-01"
parse_date("01/02/15", "%y/%m/%d")
## [1] "2001-02-15"
Ako koristimo %b
ili %B
sa ne-engleskim imenima meseci, moramo da postavimo argument locale()
. Možemo pogledati listu ugrađenih jezika sa date_names_langs()
, ili, ako naš jezik nije uključen, kreirati sopstveni date_names()
.
parse_date("1 janvier 2015", "%d %B %Y", locale = locale("fr"))
## [1] "2015-01-01"
Sada kada smo naučili da rasclanimo pojedinačni vektor, vreme je da se vratimo na početak i istražimo kako readr
raščlanjuje datoteku. Postoje dve nove stvari kojima ćemo se baviti u ovom odeljku:
Kako readr
automatski pogađa tip svake kolone.
Kako prevazići podrazumevanu specifikaciju.
readr
koristi heurističke metode da odredi tip svake kolone: On čita prvih 1000 redova i na osnovu njih određuje tip svake kolone. Ovaj proces možemo pokrenuti uz pomoć funkcije guess_parser()
, koja vraća nabolji readr
pogodak, i parse_guess()
, koja koristi tu pretpostavku za parsiranje kolone:
guess_parser("2010-10-01")
## [1] "date"
guess_parser("15:01")
## [1] "time"
guess_parser(c("TRUE", "FALSE"))
## [1] "logical"
guess_parser(c("1", "5", "9"))
## [1] "integer"
guess_parser(c("12,352,561"))
## [1] "number"
str(parse_guess("2010-10-10"))
## Date[1:1], format: "2010-10-10"
str(parse_guess(c("1", "2", "3")))
## int [1:3] 1 2 3
Funkcija isprobava svaki od sledećih tipova, dok ne pronađe podudaranje:
logički: ako sadrži samo “F”, “T”, “FALSE”, ili “TRUE”.
integer: ako sadrži samo numeričke karaktere (i -
).
double: ako sadrži samo decimalne brojeve (uključujući i brojeve kao što su 4.5e-5
).
number: ako sadrži decimalne brojeve sa oznakom grupisanja.
time: ako odgovara podrazumevanom time_format
.
date: ako odgovara podrazumevanom date_format
.
date-time: bilo koji ISO8601 datum.
Ako ne pronađe podudaranje ni sa jednim od ovih pravila, onda će kolona ostati vektor stringova.
Ove podrazumevane vrednosti nisu uvek dobre za veće datoteke. Postoje dva osnovna problema:
Prvih hiljadu redova mogu biti specijalan slučaj, pa readr
pretpostavlja tip koji nije dobar za ostale podatke. Na primer, možda imamo kolonu double-ova koja sadrži samo cele brojeve u prvih 1000 redova.
Kolona može da sadrži mnogo nedostajućih vrednosti. Ako prvih 1000 redova sadrži samo NA, readr
će pretpostaviti da je to znakovni vektor, dok verovatno želimo da ga analiziramo kao nešto specifičnije.
CSV datoteka challenge
ilustruje oba ova problema:
challenge = read_csv(readr_example("challenge.csv"))
## Parsed with column specification:
## cols(
## x = col_integer(),
## y = col_character()
## )
## Warning in rbind(names(probs), probs_f): number of columns of result is not
## a multiple of vector length (arg 1)
## Warning: 1000 parsing failures.
## row # A tibble: 5 x 5 col row col expected actual file expected <int> <chr> <chr> <chr> <chr> actual 1 1001 x no trailing characters .23837975086644292 'C:/Program Files~ file 2 1002 x no trailing characters .41167997173033655 'C:/Program Files~ row 3 1003 x no trailing characters .7460716762579978 'C:/Program Files~ col 4 1004 x no trailing characters .723450553836301 'C:/Program Files~ expected 5 1005 x no trailing characters .614524137461558 'C:/Program Files~
## ... ................. ... .......................................................................... ........ .......................................................................... ...... .......................................................................... .... .......................................................................... ... .......................................................................... ... .......................................................................... ........ ..........................................................................
## See problems(...) for more details.
(Obratite pažnju na upotrebu readr_example()
koja pronalazi putanju do datoteke uključene u paket).
Imamo dve stvari na izlazu: specifikacija kolona određena gledanjem prvih 1000 redova i informacije o prvih pet neuspelih parsiranja. Uvek je dobra ideja da se pozove problems()
, tako da nesupehe možemo detaljnije istražiti:
problems(challenge)
## # A tibble: 1,000 x 5
## row col expected actual file
## <int> <chr> <chr> <chr> <chr>
## 1 1001 x no trailing characters .23837975086644292 'C:/Program File~
## 2 1002 x no trailing characters .41167997173033655 'C:/Program File~
## 3 1003 x no trailing characters .7460716762579978 'C:/Program File~
## 4 1004 x no trailing characters .723450553836301 'C:/Program File~
## 5 1005 x no trailing characters .614524137461558 'C:/Program File~
## 6 1006 x no trailing characters .473980569280684 'C:/Program File~
## 7 1007 x no trailing characters .5784610391128808 'C:/Program File~
## 8 1008 x no trailing characters .2415937229525298 'C:/Program File~
## 9 1009 x no trailing characters .11437866208143532 'C:/Program File~
## 10 1010 x no trailing characters .2983446326106787 'C:/Program File~
## # ... with 990 more rows
Dobra strategija je da radimo kolonu po kolonu dok ne nestanu problemi. Ovde možemo da vidimo da postoji mnogo problema sa parsiranjem kolone x
- postoje decimalni brojevi posle celobrojnih vrednosti. To sugeriše da treba da koristimo double parser.
Da bismo popravili stvari, započnimo tako što ćemo kopirati i nalepiti specifikaciju kolona u originalni poziv:
challenge = read_csv(
readr_example("challenge.csv"),
col_types = cols(
x = col_integer(),
y = col_character()
)
)
## Warning in rbind(names(probs), probs_f): number of columns of result is not
## a multiple of vector length (arg 1)
## Warning: 1000 parsing failures.
## row # A tibble: 5 x 5 col row col expected actual file expected <int> <chr> <chr> <chr> <chr> actual 1 1001 x no trailing characters .23837975086644292 'C:/Program Files~ file 2 1002 x no trailing characters .41167997173033655 'C:/Program Files~ row 3 1003 x no trailing characters .7460716762579978 'C:/Program Files~ col 4 1004 x no trailing characters .723450553836301 'C:/Program Files~ expected 5 1005 x no trailing characters .614524137461558 'C:/Program Files~
## ... ................. ... .......................................................................... ........ .......................................................................... ...... .......................................................................... .... .......................................................................... ... .......................................................................... ... .......................................................................... ........ ..........................................................................
## See problems(...) for more details.
Sada možemo podesiti tip x
kolone:
challenge = read_csv(
readr_example("challenge.csv"),
col_types = cols(
x = col_double(),
y = col_character()
)
)
To rešava problem sa kolonom x
, ali ako pogledamo poslednjih nekoliko redova, videćemo da su i datumi uskladišteni u vektoru y
:
tail(challenge)
## # A tibble: 6 x 2
## x y
## <dbl> <chr>
## 1 0.805 2019-11-21
## 2 0.164 2018-03-29
## 3 0.472 2014-08-04
## 4 0.718 2015-08-16
## 5 0.270 2020-02-04
## 6 0.608 2019-01-06
Možemo i ovo popraviti tako što navedemo da je y
kolona datuma:
challenge = read_csv(
readr_example("challenge.csv"),
col_types = cols(
x = col_double(),
y = col_date()
)
)
tail(challenge)
## # A tibble: 6 x 2
## x y
## <dbl> <date>
## 1 0.805 2019-11-21
## 2 0.164 2018-03-29
## 3 0.472 2014-08-04
## 4 0.718 2015-08-16
## 5 0.270 2020-02-04
## 6 0.608 2019-01-06
Svaka parse_xyz()
funkcija ima odgovarajuću funkciju col_xyz()
. Koristimo parse_xyz()
kad su podaci već u vektoru karaktera u R-u, koristimo col_xyz()
kada želimo da kažemo readr
-u kako da učita podatke.
Preporučuje se da se uvek navede col_types()
, koji se gradi od ispisa koji daje readr
. Ovo osigurava da imamo dosledan i konzistentan princip za uvoz podataka. Ako želimo da budemo veoma strogi, koristimo stop_for_problems()
: to će izbaciti grešku i zaustaviti učitavanje ako postoje problemi sa parsiranjem.
Postoji nekoliko drugih strategija koje nam mogu pomoći da parsiramo datoteke:
challenge2 = read_csv(readr_example("challenge.csv"), guess_max = 1001)
## Parsed with column specification:
## cols(
## x = col_double(),
## y = col_date(format = "")
## )
challenge2
## # A tibble: 2,000 x 2
## x y
## <dbl> <date>
## 1 404 NA
## 2 4172 NA
## 3 3004 NA
## 4 787 NA
## 5 37 NA
## 6 2332 NA
## 7 2489 NA
## 8 1449 NA
## 9 3665 NA
## 10 3863 NA
## # ... with 1,990 more rows
challenge2 = read_csv(readr_example("challenge.csv"),
col_types = cols(.default = col_character())
)
Ovo je posebno korisno u kombinaciji sa type_convert()
, koja primenjuje heurističko parsiranje na kolone karaktera u bazi.
df = tribble(
~x, ~y,
"1", "1.21",
"2", "2.32",
"3", "4.56"
)
df
## # A tibble: 3 x 2
## x y
## <chr> <chr>
## 1 1 1.21
## 2 2 2.32
## 3 3 4.56
# Obratite paznju na tipove kolona
type_convert(df)
## Parsed with column specification:
## cols(
## x = col_integer(),
## y = col_double()
## )
## # A tibble: 3 x 2
## x y
## <int> <dbl>
## 1 1 1.21
## 2 2 2.32
## 3 3 4.56
Ako učitavamo jako veliku datoteku, možemo postaviti n_max()
na manji broj kao što je 10,000 ili 100,000, da ubrzamo, dok ne eliminišemo uobičajene probleme.
Ako imamo velike probleme u parsiranju, ponekad je lakše samo učitati čitavu liniju kao vektor karaktera sa read_lines()
, ili čak sve sa read_file()
. Tada možemo koristiti veštine parsiranja stringova, o kojima će biti reči kasnije, da bismo raščlanili komplikovanije formate.
Readr dolazi i sa dve korisne funkcije za ispis podataka u datoteke: write_csv()
i write_tsv()
. Obe funkcije povećavaju šanse da se ispravno izvrši ispis u izlaznu datoteku tako što:
Uvek kodiraju stringove kao UTF-8.
Čuvaju datume i datum-vreme u ISO8601 formatu tako da se oni mogu lako parsirati na bilo kom mestu.
Ako želimo da izvezemo csv datoteku u Excel, koristimo write_excel_csv()
- ovo piše specijalni znak (“byte order mark”) na početku datoteke koji govori Excel-u da koristimo UTF-8 kodiranje.
Najvažniji argumenti su x
(baza za čuvanje) i path
(lokacija gde se čuva). Takođe, možemo odrediti kako se zapisuju nedostajuće vrednosti argumentom na
, i dodati append
ako želimo da dopisujemo u postojeću datoteku.
write_csv(challenge, "challenge.csv")
Treba imati na umu da se informacije o tipu gube kada sačuvamo u csv.
challenge
## # A tibble: 2,000 x 2
## x y
## <dbl> <date>
## 1 404 NA
## 2 4172 NA
## 3 3004 NA
## 4 787 NA
## 5 37 NA
## 6 2332 NA
## 7 2489 NA
## 8 1449 NA
## 9 3665 NA
## 10 3863 NA
## # ... with 1,990 more rows
write_csv(challenge, "challenge-2.csv")
read_csv("challenge-2.csv")
## Parsed with column specification:
## cols(
## x = col_integer(),
## y = col_character()
## )
## Warning in rbind(names(probs), probs_f): number of columns of result is not
## a multiple of vector length (arg 1)
## Warning: 1000 parsing failures.
## row # A tibble: 5 x 5 col row col expected actual file expected <int> <chr> <chr> <chr> <chr> actual 1 1001 x no trailing characters .23837975086644292 'challenge-2.csv' file 2 1002 x no trailing characters .41167997173033655 'challenge-2.csv' row 3 1003 x no trailing characters .7460716762579978 'challenge-2.csv' col 4 1004 x no trailing characters .723450553836301 'challenge-2.csv' expected 5 1005 x no trailing characters .614524137461558 'challenge-2.csv'
## ... ................. ... ......................................................................... ........ ......................................................................... ...... ......................................................................... .... ......................................................................... ... ......................................................................... ... ......................................................................... ........ .........................................................................
## See problems(...) for more details.
## # A tibble: 2,000 x 2
## x y
## <int> <chr>
## 1 404 <NA>
## 2 4172 <NA>
## 3 3004 <NA>
## 4 787 <NA>
## 5 37 <NA>
## 6 2332 <NA>
## 7 2489 <NA>
## 8 1449 <NA>
## 9 3665 <NA>
## 10 3863 <NA>
## # ... with 1,990 more rows
Ovo čini csv-ove malo nepouzdanim za čuvanje privremenih rezultata - moramo ponovo kreirati specifikaciju kolone svaki put kada učitamo. Postoje dve alternative:
write_rds()
i read_rds()
su uniformni omotači oko osnovnih funkcija readRDS()
i saveRDS()
. One čuvaju podatke u prilagođenom R binarnom formatu koji se naziva RDS:write_rds(challenge, "challenge.rds")
read_rds("challenge.rds")
## # A tibble: 2,000 x 2
## x y
## <dbl> <date>
## 1 404 NA
## 2 4172 NA
## 3 3004 NA
## 4 787 NA
## 5 37 NA
## 6 2332 NA
## 7 2489 NA
## 8 1449 NA
## 9 3665 NA
## 10 3863 NA
## # ... with 1,990 more rows
feather
paket implementira brzi binarni format koji se može deliti preko programskih jezika:library(feather)
write_feather(challenge, "challenge.feather")
read_feather("challenge.feather")
## # A tibble: 2,000 x 2
## x y
## <dbl> <date>
## 1 404 NA
## 2 4172 NA
## 3 3004 NA
## 4 787 NA
## 5 37 NA
## 6 2332 NA
## 7 2489 NA
## 8 1449 NA
## 9 3665 NA
## 10 3863 NA
## # ... with 1,990 more rows
feather
je obično brĹži od RDS-a i upotrebljiv je izvan R-a, mada i RDS ima neke prednosti u odnosu na njega.
Moguće je raditi i sa drugim tipovima podataka, i preporučljivo je da počnemo sa paketima navedenim ispod. Oni nisu savršeni, ali su dobri za početak:
haven za SPSS, Stata i SAS datoteke.
readxl za excel datoteke (i .xls
i .xlsx
).
DBI omogućava pokretanje SQL upita.
Za hijerarhijske podatke, koristimo jsonlite za json, i xml2 za XML. Za druge podatke, možete pogledati link i rio paket.