Podstawowe operacje w R - część 2.

Operatory i funkcje

Agata Rokita

2022-11-17

Operatory

Operatory arytmetyczne

x <- 4
y <- 2

# dodawanie
x + y
## [1] 6
# odejmowanie
x - y
## [1] 2
# mnożenie
x * y
## [1] 8
# dzielenie 
x / y
## [1] 2
# modulus 
x %% y
## [1] 0
# power
x ^ y
## [1] 16

Operatory logiczne

# równe?
x == y
## [1] FALSE
# nie równe?
x != y
## [1] TRUE
# większe od?
x > y
## [1] TRUE
# mniejsze od?
x < y
## [1] FALSE
# większe lub równe od?
x >= y
## [1] TRUE
# mniejsze lub równe od?
x <= y
## [1] FALSE

Operator OR | i operator AND & są przydatne, gdy mamy do czynienia z twierdzeniami logicznymi.

(x > y) | (y < x)
## [1] TRUE
(x > y) & (y < x)
## [1] TRUE
i & są wektoryzowane, co oznacza, że można je zastosować do dwóch wektorów logicznych. Każda para jest porównywana i dla każdego elementu powstaje wynik. Wynikiem jest wektor.
c(F,F,F,T,F,F) | c(F,F,F,F,F,F)
## [1] FALSE FALSE FALSE  TRUE FALSE FALSE

Teraz istnieją również dwa warianty || i &&. Operatory te porównują elementy od lewej do prawej i zatrzymują się po pierwszym elemencie. Rozważmy ten sam przykład powyżej, zastępując | przez ||. Wynikiem jest jeden element, a nie wektor.

c(F,F,F,T,F,F) || c(F,F,F,F,F,F)
## Warning in c(F, F, F, T, F, F) || c(F, F, F, F, F, F): 'length(x) = 6 > 1' in
## coercion to 'logical(1)'

## Warning in c(F, F, F, T, F, F) || c(F, F, F, F, F, F): 'length(x) = 6 > 1' in
## coercion to 'logical(1)'
## [1] FALSE

Lepszą praktyką jest używanie any() i all() do oceny wektorów logicznych na pojedynczy logiczny.

# Czy któreś z nich jest TRUE?
any(c(F,F,F,T,F,F))
## [1] TRUE
# Czy wszystkie z nich są TRUE?
all(c(F,F,F,T,F,F))
## [1] FALSE

Znaki/łańcuchy mogą być przypisane do zmiennych w podobny sposób.

z <- "to"
z1 <- "tamto"
paste(z,z1)
## [1] "to tamto"

Funkcja paste() łączy ciągi znaków.

Nazwy zmiennych muszą być tak dobrane, aby nie kolidowały z istniejącymi zmiennymi/funkcjami. Na przykład należy unikać nazwy zmiennej c, ponieważ jest to istniejąca funkcja do konkatenacji obiektów R. Należy unikać nazwy zmiennej t, ponieważ jest to funkcja do transpozycji macierzy. Nazwy zmiennych nie mogą zaczynać się od liczby.

Operator fajki

Możesz użyć operatora pipe (%>%) w R, aby “spiąć” razem sekwencję operacji.

Operator ten jest najczęściej używany z pakietem dplyr w R do wykonywania sekwencji operacji na ramce danych.

Podstawowa składnia operatora fajki to:

dane %>% 
  zrób coś %>% 
  a potem zrób jeszcze coś %>%
  a potem wylicz/narysuj...

Operator fajki po prostu przekazuje wyniki jednej operacji do następnej operacji pod nią.

Zaletą stosowania operatora fajki jest to, że czyni on kod niezwykle łatwym do odczytania.

Fajka podstawowa

Podstawową funkcją dostarczaną przez pakiet magrittr jest %>%, lub tak zwany operator “pipe”. Operator ten przekazuje wartość, lub wynik wyrażenia, do następnego wywołania funkcji/wyrażenia. Na przykład funkcja filtrująca dane może być zapisana jako:

  filter(data, variable == numeric_value)

lub:

  data %>% filter(variable == numeric_value)

Obie funkcje wykonują to samo zadanie i korzyść z użycia %>% może nie być od razu oczywista; jednakże, gdy chcemy wykonać wiele funkcji, jej zaleta staje się oczywista.

Na przykład, jeśli chcemy przefiltrować pewne dane, pogrupować je według kategorii, podsumować je, a następnie uporządkować podsumowane wyniki, możemy wypisać to na trzy różne sposoby.

Możemy na przykład zmienić kolumny w bazowym zbiorze danych “mtcars”:

# zmieniamy nazwy kolumn
mtcars %>%
        head %>%
        set_colnames(paste("Col", 1:11, sep = ""))
##                   Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8 Col9 Col10 Col11
## Mazda RX4         21.0    6  160  110 3.90 2.62 16.5    0    1     4     4
## Mazda RX4 Wag     21.0    6  160  110 3.90 2.88 17.0    0    1     4     4
## Datsun 710        22.8    4  108   93 3.85 2.32 18.6    1    1     4     1
## Hornet 4 Drive    21.4    6  258  110 3.08 3.21 19.4    1    0     3     1
## Hornet Sportabout 18.7    8  360  175 3.15 3.44 17.0    0    0     3     2
## Valiant           18.1    6  225  105 2.76 3.46 20.2    1    0     3     1
knitr::kable(mtcars, format="html")
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160.0 110 3.90 2.62 16.5 0 1 4 4
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.88 17.0 0 1 4 4
Datsun 710 22.8 4 108.0 93 3.85 2.32 18.6 1 1 4 1
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.21 19.4 1 0 3 1
Hornet Sportabout 18.7 8 360.0 175 3.15 3.44 17.0 0 0 3 2
Valiant 18.1 6 225.0 105 2.76 3.46 20.2 1 0 3 1
Duster 360 14.3 8 360.0 245 3.21 3.57 15.8 0 0 3 4
Merc 240D 24.4 4 146.7 62 3.69 3.19 20.0 1 0 4 2
Merc 230 22.8 4 140.8 95 3.92 3.15 22.9 1 0 4 2
Merc 280 19.2 6 167.6 123 3.92 3.44 18.3 1 0 4 4
Merc 280C 17.8 6 167.6 123 3.92 3.44 18.9 1 0 4 4
Merc 450SE 16.4 8 275.8 180 3.07 4.07 17.4 0 0 3 3
Merc 450SL 17.3 8 275.8 180 3.07 3.73 17.6 0 0 3 3
Merc 450SLC 15.2 8 275.8 180 3.07 3.78 18.0 0 0 3 3
Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.25 18.0 0 0 3 4
Lincoln Continental 10.4 8 460.0 215 3.00 5.42 17.8 0 0 3 4
Chrysler Imperial 14.7 8 440.0 230 3.23 5.34 17.4 0 0 3 4
Fiat 128 32.4 4 78.7 66 4.08 2.20 19.5 1 1 4 1
Honda Civic 30.4 4 75.7 52 4.93 1.61 18.5 1 1 4 2
Toyota Corolla 33.9 4 71.1 65 4.22 1.83 19.9 1 1 4 1
Toyota Corona 21.5 4 120.1 97 3.70 2.46 20.0 1 0 3 1
Dodge Challenger 15.5 8 318.0 150 2.76 3.52 16.9 0 0 3 2
AMC Javelin 15.2 8 304.0 150 3.15 3.44 17.3 0 0 3 2
Camaro Z28 13.3 8 350.0 245 3.73 3.84 15.4 0 0 3 4
Pontiac Firebird 19.2 8 400.0 175 3.08 3.85 17.1 0 0 3 2
Fiat X1-9 27.3 4 79.0 66 4.08 1.94 18.9 1 1 4 1
Porsche 914-2 26.0 4 120.3 91 4.43 2.14 16.7 0 1 5 2
Lotus Europa 30.4 4 95.1 113 3.77 1.51 16.9 1 1 5 2
Ford Pantera L 15.8 8 351.0 264 4.22 3.17 14.5 0 1 5 4
Ferrari Dino 19.7 6 145.0 175 3.62 2.77 15.5 0 1 5 6
Maserati Bora 15.0 8 301.0 335 3.54 3.57 14.6 0 1 5 8
Volvo 142E 21.4 4 121.0 109 4.11 2.78 18.6 1 1 4 2

Fajki dodatkowe

magrittr oferuje również kilka alternatywnych operatorów pipe. Niektóre funkcje, jak np. funkcje wykreślania, powodują zakończenie łańcucha argumentów przesyłanych rurociągiem.

Operator tee (%T>%) pozwala kontynuować piping funkcji, które normalnie powodują zakończenie.

mtcars %>%
        filter(carb > 1) %>%
        extract(, 1:4) %>%
        plot() %>%
        summary()

## Length  Class   Mode 
##      0   NULL   NULL

W powyższym przykładzie normalna fajka kończy się funkcją plot() dając w efekcie NULL jako wyniki dla funkcji summary()

Wstawienie %T>% pozwala na wykreślanie i wykonywanie funkcji, które podążają za funkcją plotowania:

mtcars %>%
        filter(carb > 1) %>%
        extract(, 1:4) %T>%
        plot() %>%
        summary()

##       mpg            cyl            disp           hp     
##  Min.   :10.4   Min.   :4.00   Min.   : 76   Min.   : 52  
##  1st Qu.:15.2   1st Qu.:6.00   1st Qu.:147   1st Qu.:110  
##  Median :17.8   Median :8.00   Median :276   Median :175  
##  Mean   :18.6   Mean   :6.64   Mean   :258   Mean   :164  
##  3rd Qu.:21.0   3rd Qu.:8.00   3rd Qu.:351   3rd Qu.:205  
##  Max.   :30.4   Max.   :8.00   Max.   :472   Max.   :335

Operator przypisania złożonego %<>% jest używany do aktualizowania wartości poprzez najpierw wprowadzenie jej do jednego lub więcej wyrażeń, a następnie przypisanie wyniku.

Na przykład, powiedzmy, że chcesz przekształcić zmienną mpg w ramce danych mtcars na pomiar pierwiastka kwadratowego. Użycie %<>% spowoduje wykonanie funkcji po prawej stronie %<>% i zapisanie zmian, jakie te funkcje wykonują, do zmiennej lub ramki danych wywołanej po lewej stronie %<>%.

mtcars$mpg %<>% sqrt

head(mtcars)
##                    mpg cyl disp  hp drat   wt qsec vs am gear carb
## Mazda RX4         4.58   6  160 110 3.90 2.62 16.5  0  1    4    4
## Mazda RX4 Wag     4.58   6  160 110 3.90 2.88 17.0  0  1    4    4
## Datsun 710        4.77   4  108  93 3.85 2.32 18.6  1  1    4    1
## Hornet 4 Drive    4.63   6  258 110 3.08 3.21 19.4  1  0    3    1
## Hornet Sportabout 4.32   8  360 175 3.15 3.44 17.0  0  0    3    2
## Valiant           4.25   6  225 105 2.76 3.46 20.2  1  0    3    1
knitr::kable(mtcars, format="html")
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 4.58 6 160.0 110 3.90 2.62 16.5 0 1 4 4
Mazda RX4 Wag 4.58 6 160.0 110 3.90 2.88 17.0 0 1 4 4
Datsun 710 4.78 4 108.0 93 3.85 2.32 18.6 1 1 4 1
Hornet 4 Drive 4.63 6 258.0 110 3.08 3.21 19.4 1 0 3 1
Hornet Sportabout 4.32 8 360.0 175 3.15 3.44 17.0 0 0 3 2
Valiant 4.25 6 225.0 105 2.76 3.46 20.2 1 0 3 1
Duster 360 3.78 8 360.0 245 3.21 3.57 15.8 0 0 3 4
Merc 240D 4.94 4 146.7 62 3.69 3.19 20.0 1 0 4 2
Merc 230 4.78 4 140.8 95 3.92 3.15 22.9 1 0 4 2
Merc 280 4.38 6 167.6 123 3.92 3.44 18.3 1 0 4 4
Merc 280C 4.22 6 167.6 123 3.92 3.44 18.9 1 0 4 4
Merc 450SE 4.05 8 275.8 180 3.07 4.07 17.4 0 0 3 3
Merc 450SL 4.16 8 275.8 180 3.07 3.73 17.6 0 0 3 3
Merc 450SLC 3.90 8 275.8 180 3.07 3.78 18.0 0 0 3 3
Cadillac Fleetwood 3.23 8 472.0 205 2.93 5.25 18.0 0 0 3 4
Lincoln Continental 3.23 8 460.0 215 3.00 5.42 17.8 0 0 3 4
Chrysler Imperial 3.83 8 440.0 230 3.23 5.34 17.4 0 0 3 4
Fiat 128 5.69 4 78.7 66 4.08 2.20 19.5 1 1 4 1
Honda Civic 5.51 4 75.7 52 4.93 1.61 18.5 1 1 4 2
Toyota Corolla 5.82 4 71.1 65 4.22 1.83 19.9 1 1 4 1
Toyota Corona 4.64 4 120.1 97 3.70 2.46 20.0 1 0 3 1
Dodge Challenger 3.94 8 318.0 150 2.76 3.52 16.9 0 0 3 2
AMC Javelin 3.90 8 304.0 150 3.15 3.44 17.3 0 0 3 2
Camaro Z28 3.65 8 350.0 245 3.73 3.84 15.4 0 0 3 4
Pontiac Firebird 4.38 8 400.0 175 3.08 3.85 17.1 0 0 3 2
Fiat X1-9 5.22 4 79.0 66 4.08 1.94 18.9 1 1 4 1
Porsche 914-2 5.10 4 120.3 91 4.43 2.14 16.7 0 1 5 2
Lotus Europa 5.51 4 95.1 113 3.77 1.51 16.9 1 1 5 2
Ford Pantera L 3.98 8 351.0 264 4.22 3.17 14.5 0 1 5 4
Ferrari Dino 4.44 6 145.0 175 3.62 2.77 15.5 0 1 5 6
Maserati Bora 3.87 8 301.0 335 3.54 3.57 14.6 0 1 5 8
Volvo 142E 4.63 4 121.0 109 4.11 2.78 18.6 1 1 4 2

Niektóre funkcje (np. lm, aggregate, cor) posiadają argument data, który pozwala na bezpośrednie użycie nazw wewnątrz danych jako części wywołania.

Operator ekspozycji (%$%) jest przydatny, gdy chcemy przekazać ramkę danych, która może zawierać wiele kolumn, do funkcji, która jest stosowana tylko do niektórych kolumn.

Na przykład, funkcja korelacji (cor) wymaga tylko argumentu x i y, więc jeśli przesyłasz dane mtcars do funkcji cor używając %>%, otrzymasz błąd, ponieważ cor nie wie, jak obsługiwać mtcars. Jednak użycie %$% pozwala ci powiedzieć “weź tę ramkę danych, a następnie wykonaj cor() na tych określonych kolumnach w mtcars.”

mtcars %>%
        subset(vs == 0) %>%
        cor(mpg, wt)
## Error in pmatch(use, c("all.obs", "complete.obs", "pairwise.complete.obs", : object 'wt' not found

Błąd no nie? Użycie %$% pozwala na wybranie zmiennych:

mtcars %>%
        subset(vs == 0) %$%
        cor(mpg, wt)
## [1] -0.831

Inne operatory

Oto kilka innych powszechnie używanych operatorów.

jest używany do generowania sekwencji.

1:10
##  [1]  1  2  3  4  5  6  7  8  9 10

%in% jest operatorem zbioru.

“a” %in% c(“x”, “p”, “a”, “c”) sprawdza, czy a jest członkiem zbioru x,p,a,c.

"a" %in% c("x","p","a","c")
## [1] TRUE

Odwrotność również by działała, ale liczba zwróconych elemntów jest teraz inna.

c("x","p","a","c") %in% "a"
## [1] FALSE FALSE  TRUE FALSE

:: jest używany do bezpośredniego dostępu do funkcji z określonego pakietu. Kiedy normalnie wywołujesz funkcję, powiedzmy sum(), R przeszukuje przestrzeń nazw i znajduje ją. W przypadku, gdy istnieje funkcja sum() z innego pakietu, ostatnio załadowany pakiet zastępuje poprzednią funkcję. W takich przypadkach lub aby być całkowicie pewnym, że używasz właściwej funkcji z właściwego pakietu, możesz użyć package::function() do wywołania funkcji. Na przykład; base::sum(). ::: jest używane do wywoływania funkcji z pakietu, które nie są “eksportowane”. Jest to rzadko używane.

%*% jest używane do mnożenia macierzy.

matrix(c(2,2,3,3),nrow=2) %*% matrix(c(4,2,5,3),nrow=2)
##      [,1] [,2]
## [1,]   14   19
## [2,]   14   19
matrix(c(2,2,3,3),nrow=2) %*% c(6,6)
##      [,1]
## [1,]   30
## [2,]   30

Operacje na datach

Operacje na datach i format daty nie są takie proste i oczywiste jak by się wydawało.

Proszę prześledź kilka poniższych przykładów operacji z datami.

x <- "2013-04-03"
# Jaka jest struktura x?
str(x)
##  chr "2013-04-03"
# Użyj as.Date() aby zmienić format na datę
x_date <- as.Date(x)
str(x_date)
##  Date[1:1], format: "2013-04-03"
# Zamień April 10 2014 jako datę
april_10_2014 <- as.Date("2014-04-10")

Użyjmy pakietu readr. Co wykonują poniższe czynności?

as.POSIXct("2010-10-01 12:12:00")
## [1] "2010-10-01 12:12:00 CEST"
as.POSIXct("2010-10-01 12:12:00", tz = "America/Los_Angeles")
## [1] "2010-10-01 12:12:00 PDT"

Bardzo przydatny jest pakiet “lubridate” dla operacji z datami.

library(lubridate)
x <- "2010 September 20th" # 2010-09-20
ymd(x)
## [1] "2010-09-20"
y <- "02.01.2010"  # 2010-01-02
dmy(y)
## [1] "2010-01-02"
z <- "Sep, 12th 2010 14:00"  # 2010-09-12T14:00
mdy_hm(z)
## [1] "2010-09-12 14:00:00 UTC"

Możemy także w dowolny sposób manipulować formatem wyświetlania dat.

# Kolejność:
x <- "Monday June 1st 2010 at 4pm"
parse_date_time(x, orders = "amdyIp")
## [1] "2010-06-01 16:00:00 UTC"
two_orders <- c("October 7, 2001", "October 13, 2002", "April 13, 2003",  "17 April 2005", "23 April 2017")
parse_date_time(two_orders, orders = c("mdy","dmy"))
## [1] "2001-10-07 UTC" "2002-10-13 UTC" "2003-04-13 UTC" "2005-04-17 UTC"
## [5] "2017-04-23 UTC"
short_dates <- c("11 December 1282", "May 1372", "1253")
myorders <- c("dOmY", "OmY", "Y")
parse_date_time(short_dates, orders = myorders)
## [1] "1282-12-11 UTC" "1372-05-01 UTC" "1253-01-01 UTC"

Funkcje pakietu lubridate pozwalają nam również zaokrąglać formaty dat.

r_3_4_1 <- ymd_hms("2016-05-03 07:13:28 UTC")
# Zaokrąglij w dół do dni
floor_date(r_3_4_1, unit = "day")
## [1] "2016-05-03 UTC"
# Zaokrąglij do 5 minut
round_date(r_3_4_1, unit = "5 minutes")
## [1] "2016-05-03 07:15:00 UTC"
# Zaokrąglij do tygodni 
ceiling_date(r_3_4_1, unit = "week")
## [1] "2016-05-08 UTC"
# Odejmij od r_3_4_1 ją samą i zaokrąglij do dni
r_3_4_1 - floor_date(r_3_4_1, unit = "day")
## Time difference of 7.22 hours

Rozpatrzmy funkcje obliczeniowe dat:

# Lądowanie na księżycu :)
date_landing <- mdy("July 20, 1969")
moment_step <- mdy_hms("July 20, 1969, 02:56:15", tz = "UTC")
# Ile dni już upłynęło??
difftime(today(), date_landing, units = "days")
## Time difference of 19478 days
# Ile sekund?
difftime(now(),moment_step, units = "secs")
## Time difference of 1682960518 secs

Możemy łatwo obliczać różnice między datami:

# 3 daty
mar_11 <- ymd_hms("2017-03-11 12:00:00", 
  tz = "America/Los_Angeles")
mar_12 <- ymd_hms("2017-03-12 12:00:00", 
  tz = "America/Los_Angeles")
mar_13 <- ymd_hms("2017-03-13 12:00:00", 
  tz = "America/Los_Angeles")
# Różnica pomiędzy mar_13 oraz mar_12 w sekundach
difftime(mar_13, mar_12, units = "secs")
## Time difference of 86400 secs

Możemy również manipulować datami, dodawać lub odejmować okresy:

# Dodaj jeden tydzień do mon_2pm
mon_2pm <- dmy_hm("27 Aug 2018 14:00")
mon_2pm + weeks(1)
## [1] "2018-09-03 14:00:00 UTC"
# Dodaj 81 godzin do tue_9am
tue_9am <- dmy_hm("28 Aug 2018 9:00")
tue_9am + dhours(81)
## [1] "2018-08-31 18:00:00 UTC"
# Odejmij 5 lat od today()
today() - years(5)
## [1] "2017-11-17"
# Odejmij 5 lat od today()
today() - dyears(5)
## [1] "2017-11-16 18:00:00 UTC"

Możemy również manipulować sekwencjami:

# Dodaj 8 godzin:
today_8am <- today() + hours(8)
# Sekwencja 1 do 26:
every_two_weeks <- 1:26 * weeks(2)
# Utwórz sekwencję dat co 2 tygodnie przez rok:
every_two_weeks  + today_8am
##  [1] "2022-12-01 08:00:00 UTC" "2022-12-15 08:00:00 UTC"
##  [3] "2022-12-29 08:00:00 UTC" "2023-01-12 08:00:00 UTC"
##  [5] "2023-01-26 08:00:00 UTC" "2023-02-09 08:00:00 UTC"
##  [7] "2023-02-23 08:00:00 UTC" "2023-03-09 08:00:00 UTC"
##  [9] "2023-03-23 08:00:00 UTC" "2023-04-06 08:00:00 UTC"
## [11] "2023-04-20 08:00:00 UTC" "2023-05-04 08:00:00 UTC"
## [13] "2023-05-18 08:00:00 UTC" "2023-06-01 08:00:00 UTC"
## [15] "2023-06-15 08:00:00 UTC" "2023-06-29 08:00:00 UTC"
## [17] "2023-07-13 08:00:00 UTC" "2023-07-27 08:00:00 UTC"
## [19] "2023-08-10 08:00:00 UTC" "2023-08-24 08:00:00 UTC"
## [21] "2023-09-07 08:00:00 UTC" "2023-09-21 08:00:00 UTC"
## [23] "2023-10-05 08:00:00 UTC" "2023-10-19 08:00:00 UTC"
## [25] "2023-11-02 08:00:00 UTC" "2023-11-16 08:00:00 UTC"

Funkcje

Funkcje, które są dostarczane z bazową instalacją R, są określane jako funkcje wbudowane lub funkcje bazowe.

R ma mnóstwo wbudowanych funkcji do różnych zastosowań, takich jak analiza danych, programowanie, matematyka, wykreślanie itp.

Dodatkowe funkcje mogą być udostępnione poprzez zainstalowanie zewnętrznych pakietów.

Funkcje podstawowe

Poniżej przedstawiono kilka funkcji, które można zastosować do danych liczbowych:

# wygeneruj 10 liczb losowych z zakresu od 1 do 200
x <- sample(x=1:200,10)

# długość
length(x)
## [1] 10
# suma
sum(x)
## [1] 755
# średnia
mean(x)
## [1] 75.5
# mediana
median(x)
## [1] 67
# minimum
min(x)
## [1] 15
# logarytm
log(x)
##  [1] 4.23 3.81 3.89 4.37 2.71 4.17 4.64 4.76 3.09 5.25
# exponent
exp(x)
##  [1]                                                      925378172558778718688878002176
##  [2]                                                                34934271057485094912
##  [3]                                                              1907346572495099789312
##  [4]                                                 20382810665126688239604997451415552
##  [5]                                                                             3269017
##  [6]                                                       16948892444103337803358666752
##  [7]                                      1467662230155442385345160162931037779871662080
##  [8]                                 649313425566446212204527603098723999605535388205056
##  [9]                                                                          3584912846
## [10] 32805870153846704713003703822495292426493650066588991738813532693125184167792869376
# pierwiastek
sqrt(x)
##  [1]  8.31  6.71  7.00  8.89  3.87  8.06 10.20 10.82  4.69 13.78
# zaokrąglanie
round(x) 
##  [1]  69  45  49  79  15  65 104 117  22 190
#Domyślnie zaokrągla wartości do 0 miejsc po #przecinku. Wypróbuj ?round w konsoli dla odmian #round() i sposobów zmiany liczby cyfr do #zaokrąglenia.  

Funkcje użytkowe

R posiada kilka funkcji do żonglowania strukturami danych:

seq(): Generuje sekwencje, określając argumenty from, to, oraz by.

rep(): Replikuj elementy wektorów i list.

sort(): Sortuj wektor w porządku rosnącym. Działa na liczbach, ale także na łańcuchach znaków i logikach.

rev(): Odwraca elementy w strukturach danych, dla których zdefiniowano odwracanie.

str(): Wyświetl strukturę dowolnego obiektu R.

append(): Połącz wektory lub listy.

is.*(): Sprawdź klasę obiektu R.

as.*(): Konwertowanie obiektu R z jednej klasy na inną.

unlist(): Spłaszcz (ewentualnie osadzone) listy, aby uzyskać wektor.

Funkcje tekstowe

Kilka przydatnych funkcji związanych z ciągami znaków:

a <- "sunny"
b <- "day"

# połącz
paste(a, b)
## [1] "sunny day"
# odnajdź wzór
grep("sun", a)
## [1] 1
# liczba znaków
nchar("sunny")
## [1] 5
# do dużej litery
toupper("sunny")
## [1] "SUNNY"
# do małej litery
tolower("SUNNY")
## [1] "sunny"
# zamiana wzoru
sub("sun", "fun", "sunny")
## [1] "funny"
# podciąg
substr("sunny", start=1, stop=3)
## [1] "sun"

grepl & grep

W swojej najbardziej podstawowej formie, wyrażenia regularne mogą być użyte do sprawdzenia, czy dany wzorzec istnieje wewnątrz łańcucha znaków lub wektora łańcuchów znaków.

W tym celu można użyć:

grepl(), które zwraca TRUE, gdy wzorzec zostanie znaleziony w odpowiednim łańcuchu znaków.

grep(), która zwraca wektor indeksów łańcuchów znaków zawierających wzorzec.

Obie funkcje potrzebują wzorca i argumentu x, gdzie wzorzec jest wyrażeniem regularnym, które chcesz dopasować, a argument x jest wektorem znaków, z którego należy szukać dopasowań.

W tym i następnych ćwiczeniach, będziemy manipulować wektorem znaków adresów e-mail!

Wektor emaili został już zdefiniowany, więc możesz od razu przystąpić do wykonywania instrukcji!

# Wektor maili został już zdefiniowany dla Ciebie:
emails <- c("john.doe@ivyleague.edu", "education@world.gov", "dalai.lama@peace.org",
            "invalid.edu", "quant@bigdatacollege.edu", "cookie.monster@sesame.tv")

# Użyj grepl() aby dopasować dla "edu"
grepl(pattern = "edu", x = emails)
## [1]  TRUE  TRUE FALSE  TRUE  TRUE FALSE
# Użyj grep() aby dopasować dla "edu", zapisz wynik do hits
 
hits <- grep(pattern = "edu", x = emails)

# Podzestaw emaili używając hits
emails[hits]
## [1] "john.doe@ivyleague.edu"   "education@world.gov"     
## [3] "invalid.edu"              "quant@bigdatacollege.edu"

Możesz użyć znaku starszeństwa, ^, i znaku dolara, $, aby dopasować zawartość znajdującą się odpowiednio w start oraz end ciągu tekstowego.

To może nas przybliżyć o krok do poprawnego wzorca dopasowującego tylko adresy e-mail ".edu" z naszej listy e-maili.

Ale jest jeszcze więcej rzeczy, które można dodać, aby uczynić wzorzec bardziej solidnym:

@, ponieważ poprawny email musi zawierać znak at.
.*, który pasuje do dowolnego znaku (.) zero lub więcej razy (*). Zarówno dot jak i asterisk są metaznakami.

Można ich użyć do dopasowania dowolnego znaku pomiędzy znakiem at a częścią “.edu” adresu email.

Aby dopasować część “edu” adresu e-mail na końcu łańcucha, należy użyć znaku dot i asterisk.

Część \\ ucieka od kropki: mówi R, że chcesz użyć . jako rzeczywistego znaku.

# Wektor emaili został już zdefiniowany dla Ciebie
emails <- c("john.doe@ivyleague.edu", "education@world.gov", "dalai.lama@peace.org",
            "invalid.edu", "quant@bigdatacollege.edu", "cookie.monster@sesame.tv")

# Użyj grepl() aby dopasować adresy .edu bardziej solidnie
grepl(pattern = "@.edu$", x = emails)
## [1] FALSE FALSE FALSE FALSE FALSE FALSE
# Użyj grep(), aby dopasować adresy .edu bardziej solidnie, zapisując wynik w hits
hits <- grep(pattern = "@.*edu$", x = emails)

# Podzestaw emaili używając hits
emails[hits]
## [1] "john.doe@ivyleague.edu"   "quant@bigdatacollege.edu"

sub i gsub

Podczas gdy grep() i grepl() były używane do prostego sprawdzenia, czy wyrażenie regularne może być dopasowane do wektora znaków, sub() i gsub() idą o krok dalej: można podać argument zamiany. Jeśli wewnątrz wektora znaków x, wzór wyrażenia regularnego zostanie znaleziony, pasujące elementy zostaną zastąpione replacement.sub() zastępuje tylko pierwsze dopasowanie, podczas gdy gsub() zastępuje wszystkie dopasowania.

# Wektor emails został już zdefiniowany dla Ciebie
emails <- c("john.doe@ivyleague.edu", "education@world.gov", "global@peace.org",
"invalid.edu", "quant@bigdatacollege.edu", "cookie.monster@sesame.tv")

# Użyj sub() aby przekonwertować domeny email na pg.edu.pl
sub(pattern = "@*.edu$", replacement = "@pg.edu.pl", x = emails)
## [1] "john.doe@ivyleague@pg.edu.pl"   "education@world.gov"           
## [3] "global@peace.org"               "invalid@pg.edu.pl"             
## [5] "quant@bigdatacollege@pg.edu.pl" "cookie.monster@sesame.tv"
emails
## [1] "john.doe@ivyleague.edu"   "education@world.gov"     
## [3] "global@peace.org"         "invalid.edu"             
## [5] "quant@bigdatacollege.edu" "cookie.monster@sesame.tv"
# Dlaczego nie wymieniono wszystkich adresów??? ;-)
# Wymienione zostały jedynie te które miały edu w nazwie

Wyrażenia regularne to typowa koncepcja, której nauczysz się poprzez działanie i oglądanie innych przykładów. Zanim zaczniesz się zastanawiać nad wyrażeniem regularnym w tym ćwiczeniu, spójrz na nowe rzeczy, które zostaną użyte:

.*: Zwykły podejrzany! Można go odczytać jako “każdy znak, który jest dopasowany zero lub więcej razy”.

\\s: Dopasowanie spacji. S jest normalnie znakiem, ucieczka od niego (\\) czyni go metaznakiem.

[0-9]+: Dopasuj liczby od 0 do 9, przynajmniej raz (+).

([0-9]+): Nawiasy służą do udostępnienia części pasującego łańcucha do zdefiniowania zastępstwa. Nawias w argumencie replacement z sub() zostaje ustawiony na łańcuch, który jest przechwytywany przez wyrażenie regularne [0-9]+.

awards <- c("Won 1 Oscar.",
  "Won 1 Oscar. Another 9 wins & 24 nominations.",
  "1 win and 2 nominations.",
  "2 wins & 3 nominations.",
  "Nominated for 2 Golden Globes. 1 more win & 2 nominations.",
  "4 wins & 1 nomination.")

# Co zwraca ten fragment kodu?
sub(".*\\s([0-9]+)\\snomination.*$", "\\1", awards)
## [1] "Won 1 Oscar." "24"           "2"            "3"            "2"           
## [6] "1"
#fragment kodu zwraca ilość nominacji "pobierając liczbę, która znajduje się przed słowem nomination

Czy możesz wyjaśnić, dlaczego co się dokładnie stało?

Otóż ([0-9]+) wybiera całą liczbę, która znajduje się przed słowem “nominacje” w ciągu, a całe dopasowanie zostaje zastąpione tą liczbą ze względu na odniesienie do treści wewnątrz nawiasów.

Funkcje ogólne

Kilka ogólnych funkcji:

print("hello")
## [1] "hello"
print("world")
## [1] "world"
cat("hello")
## hello
cat(" world")
##  world
cat("\nhello\nworld")
## 
## hello
## world

Jeśli widzisz, że kopiujesz fragment kodu R wiele razy, to prawdopodobnie dobrym pomysłem jest stworzenie z niego funkcji.

Załóżmy, że masz dwa wektory, na których chciałbyś wykonać serię operacji, a następnie wyprowadzić wynik.

a <- 1:6
b <- 8:10

d <- a*b
e <- log(d)
f <- sqrt(e)
f
## [1] 1.44 1.70 1.84 1.86 1.95 2.02

Możesz zmodyfikować ten blok kodu w funkcję w następujący sposób:

my_function <- function(a, b){
  d <- a*b
  e <- log(d)
  f <- sqrt(e)
  return(f)
}

Po zdefiniowaniu możesz użyć tego, gdziekolwiek jest to potrzebne:

my_function(a=2:4, b=6:8)
## [1] 1.58 1.74 1.86

Zmienne zdefiniowane wewnątrz funkcji są dostępne tylko wewnątrz tej funkcji i nie są dostępne poza nią, chyba że zostaną zwrócone:

my_new_function <- function(a) {
  varz <- a + 2
  return(varz)
}

my_new_function(5)
## [1] 7
#print(varz) # zmienne zdefiniowane w funkcji nie są dostępne poza funkcją, wyświetli się error

W powyższej funkcji zmienna varz utworzona wewnątrz funkcji nie jest dostępna poza nią.

Możliwa jest jednak sytuacja odwrotna:

my_new_function <- function() {
  varz <- foo + 2
  return(varz)
}

foo <- 55
my_new_function()
## [1] 57

W powyższym przykładzie zmienna foo użyta wewnątrz funkcji nie jest dostarczona do funkcji poprzez argument, a mimo to udaje się ją znaleźć. Gdy funkcja nie znajduje zmiennej wewnątrz funkcji, szuka jej poza nią.

Funkcje warunkowe

Instrukcje warunkowe pisze się za pomocą if().

a <- 2
b <- 5

if(a < b) print(paste(a,"jest mniejsze od",b))
## [1] "2 jest mniejsze od 5"

else służy do dodania alternatywnego wyjścia:

a <- 2
b <- 5

if(a < b) {
  print(paste(a,"jest mniejsze od",b))
}else{
  print(paste(b,"jest mniejsze od",a))
}
## [1] "2 jest mniejsze od 5"
a <- 60
b <- 10

if(a < b) {
  print(paste(a,"jest mniejsze od",b))
}else{
  print(paste(b,"jest mniejsze od",a))
}
## [1] "10 jest mniejsze od 60"

instrukcje if else mogą być łączone w łańcuchy:

grade <- "B"

if(grade == "A"){
  print("Ocena jest bardzo dobra!")
}else if(grade == "B"){
  print("Ocena jest dobra.")
} else if (grade == "C") {
  print("Ocena dostateczna.")
}
## [1] "Ocena jest dobra."

Nie należy tego mylić z funkcją o nazwie ifelse(). Jest ona również używana do wyboru warunkowego i przyjmuje postać ifelse(test, return-if-yes, return-if-no), a to jest wektorowe. Tak więc, na przykład, oto kilka wieków osób. Sklasyfikuj je jako dorosłych lub młodocianych.

x <- c(6,23,12,10,56,44)
ifelse(x>18,"Dorosły", "Młodociany")
## [1] "Młodociany" "Dorosły"    "Młodociany" "Młodociany" "Dorosły"   
## [6] "Dorosły"

Pętla for() jest przydatna do wielokrotnego uruchamiania poleceń przez znaną liczbę iteracji.

for (i in 1:5){
  print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5

Pętla while() jest przydatna do wielokrotnego uruchamiania poleceń przez nieznaną liczbę iteracji, aż do spełnienia jakiegoś warunku.

i <- 1
while(i < 5){
  print(i)
  i <- i+1
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4

Struktura tibble

W całej tej książce pracujemy z “tibbles” zamiast tradycyjnego data.frame w R. Tibbles są ramkami danych, ale poprawiają niektóre starsze zachowania, aby ułatwić życie. R jest starym językiem i niektóre rzeczy, które były przydatne 10 lub 20 lat temu, teraz stają na drodze.

Trudno jest zmienić bazę R bez łamania istniejącego kodu, więc większość innowacji pojawia się w pakietach. Tutaj zobaczmy jak działa tibble, który zapewnia opinane ramki danych, które sprawiają, że praca w tidyverse jest nieco łatwiejsza. W większości analiz używa się terminu tibble i data frame zamiennie.

library(tidyverse)
tibble(
  x = 1:5, 
  y = 1, 
  z = x ^ 2 + y
)
## # A tibble: 5 × 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

Jeśli jesteś już zaznajomiony z data.frame(), zauważ, że tibble() robi znacznie mniej: nigdy nie zmienia typu danych wejściowych (np. nigdy nie konwertuje ciągów znaków na współczynniki!), nigdy nie zmienia nazw zmiennych i nie tworzy nazw wierszy.

Główne różnice między tibble a ramką danych:

Tibble mają wyrafinowaną metodę drukowania, która pokazuje tylko pierwsze 10 wierszy i wszystkie kolumny, które mieszczą się na ekranie. To znacznie ułatwia pracę z dużymi danymi. Oprócz nazwy, każda kolumna podaje swój typ, co jest miłą cechą zapożyczoną z str()

Tibbles są zaprojektowane tak, abyś przypadkowo nie przytłoczył swojej konsoli, gdy drukujesz duże ramki danych. Ale czasami potrzebujesz więcej danych wyjściowych niż domyślny wyświetlacz. Istnieje kilka opcji, które mogą pomóc.

Zadanie do wykonania

Zadanie wykonaj w kilku etapach:

  1. Zapisz “dane” jako obiekt tibble pod inną nazwą, np. dane2. Porównaj je.
library("tidyverse")
dane2 <- tibble(dane)
dane2
## # A tibble: 985 × 12
##    street       city    zip state  beds baths sq__ft type  sale_…¹ price latit…²
##    <chr>        <chr> <int> <chr> <int> <int>  <int> <chr> <chr>   <int>   <dbl>
##  1 3526 HIGH ST SACR… 95838 CA        2     1    836 Resi… Wed Ma… 59222    38.6
##  2 51 OMAHA CT  SACR… 95823 CA        3     1   1167 Resi… Wed Ma… 68212    38.5
##  3 2796 BRANCH… SACR… 95815 CA        2     1    796 Resi… Wed Ma… 68880    38.6
##  4 2805 JANETT… SACR… 95815 CA        2     1    852 Resi… Wed Ma… 69307    38.6
##  5 6001 MCMAHO… SACR… 95824 CA        2     1    797 Resi… Wed Ma… 81900    38.5
##  6 5828 PEPPER… SACR… 95841 CA        3     1   1122 Condo Wed Ma… 89921    38.7
##  7 6048 OGDEN … SACR… 95842 CA        3     2   1104 Resi… Wed Ma… 90895    38.7
##  8 2561 19TH A… SACR… 95820 CA        3     1   1177 Resi… Wed Ma… 91002    38.5
##  9 11150 TRINI… RANC… 95670 CA        2     2    941 Condo Wed Ma… 94905    38.6
## 10 7325 10TH ST RIO … 95673 CA        3     2   1146 Resi… Wed Ma… 98937    38.7
## # … with 975 more rows, 1 more variable: longitude <dbl>, and abbreviated
## #   variable names ¹​sale_date, ²​latitude
library("dplyr")
paste("Porównaj dane i dane2", all_equal(dane, dane2))
## [1] "Porównaj dane i dane2 TRUE"
library("arsenal")
## 
## Attaching package: 'arsenal'
## The following object is masked from 'package:lubridate':
## 
##     is.Date
## The following object is masked from 'package:magrittr':
## 
##     set_attr
comparedf(dane, dane2)
## Compare Object
## 
## Function Call: 
## comparedf(x = dane, y = dane2)
## 
## Shared: 12 non-by variables and 985 observations.
## Not shared: 0 variables and 0 observations.
## 
## Differences found in 0/12 variables compared.
## 0 variables compared have non-identical attributes.
  1. Które ze zmiennych przekształcone powinny być w czynnik (factor)?
#factors używane są przy zmiennych, które mają ustaloną możliwą wartość
#zmieniłabym na factor dla: city, state, type w celach filtrowania
dane2$city <- as.factor(dane2$city)
dane2$state <- as.factor(dane2$state)
dane2$type <- as.factor(dane2$type)

#pokazanie czy zadziałało na przykładzie
class(dane2$type)
## [1] "factor"
  1. Cena nieruchomości (price) jest obecnie integer. Czy to odpowiednie?
#w przypadku jeśli price nie jest z liczbą po przecinku tak, jednak zmieniłabym na double żeby otrzymać bardziej dokładny wynik na wypadek jeśli coś byłoby po przecinku
dane2$price <- as.double(dane2$price)
class(dane2$price)
## [1] "numeric"
  1. Napisz własną funkcję, która wykonywać będzie standaryzowanie zmiennych.
standaryzacja <- function(c) {
  c <- (c - mean(c))/sd(c)
  return(c)
}

#funkcja standaryzacja 
#wartosc oryginalna - srednia / odchylenie standartowe
  1. Utwórz nową zmienną “cena” w tibble “dane2”, która będzie zestandaryzowaną price, korzystając z własnej funkcji.
dane2$cena <- standaryzacja(dane2$price)
dane2
## # A tibble: 985 × 13
##    street       city    zip state  beds baths sq__ft type  sale_…¹ price latit…²
##    <chr>        <fct> <int> <fct> <int> <int>  <int> <fct> <chr>   <dbl>   <dbl>
##  1 3526 HIGH ST SACR… 95838 CA        2     1    836 Resi… Wed Ma… 59222    38.6
##  2 51 OMAHA CT  SACR… 95823 CA        3     1   1167 Resi… Wed Ma… 68212    38.5
##  3 2796 BRANCH… SACR… 95815 CA        2     1    796 Resi… Wed Ma… 68880    38.6
##  4 2805 JANETT… SACR… 95815 CA        2     1    852 Resi… Wed Ma… 69307    38.6
##  5 6001 MCMAHO… SACR… 95824 CA        2     1    797 Resi… Wed Ma… 81900    38.5
##  6 5828 PEPPER… SACR… 95841 CA        3     1   1122 Condo Wed Ma… 89921    38.7
##  7 6048 OGDEN … SACR… 95842 CA        3     2   1104 Resi… Wed Ma… 90895    38.7
##  8 2561 19TH A… SACR… 95820 CA        3     1   1177 Resi… Wed Ma… 91002    38.5
##  9 11150 TRINI… RANC… 95670 CA        2     2    941 Condo Wed Ma… 94905    38.6
## 10 7325 10TH ST RIO … 95673 CA        3     2   1146 Resi… Wed Ma… 98937    38.7
## # … with 975 more rows, 2 more variables: longitude <dbl>, cena <dbl>, and
## #   abbreviated variable names ¹​sale_date, ²​latitude
  1. Wykreśl cenę oraz price na wykresach ramkowych obok siebie na jednym ekranie. Różnice?
par(mfrow=c(1,2))

#wykres ramkowy dla cena
wykres1 <- boxplot(dane2$cena, data=airquality,
main="Wykres dla cena", ylab="Cena",
col="lightgreen", border="darkmagenta")

#wykres ramkowy dla price
wykres2 <- boxplot(dane2$price, data=airquality,
main="Wykres dla price", ylab="Price",
col="grey",border="darkmagenta")

#wniosek: po standaryzacji wykresy wyglądają tak samo dla price i cena