library(tidyverse) library(rlang) library(sloop) # for OOP library(nycflights13) # for examples flights2 <- flights %>% select(year:day, hour, tailnum, carrier, origin, dest) airports2 <- airports %>% select(faa, name) stocks <- tibble(year = sample(2011:2015, rep=T, 8), qtr = sample(1:4, rep=T, 8), return = c(1.88, 0.59, 0.35, NA, 0.92, NA, 2.66, 3.55)) %>% unique # definitionen students <- tibble( name = c("Alice", "Bob", "Carl", "Dave", "Eve"), grade = c(1, 2, 2, 3, 1) ) info <- tibble( grade = 3:1, desc = c("Poor", "Good", "Excellent"), fail = c(T, F, F) ) # Sequenzen 1:42 0.5:-3.9 seq(0, 3, by=0.75) seq(0.5, -3.9, len=5) rep(c(0,2), times=5) rep(c(0,2), each=5) cumsum(1:10) # Indexing x <- 21:42 x[c(3, 7, 6, 7.9)] # position x[-c(1, 3, 5)] # ausschluss von angegebenen positionen x[FALSE] # integer(0) seq_along(x[FALSE]) # zuverlässiger 1:length(x[FALSE]) x[] # nichts: alles wird zurückgegeben x[x %% 2 == 0] # wahrheitswerte x[(seq_along(x)-1) %% 5 == 0] x[c(T, F)] # jedes zweite, da T, F recycled wird y <- structure(x, names=letters[seq_along(x)]) y[c("a", "j", "e", "e", "z")] # namen y[setdiff(names(y), c("a", "e", "z"))] # analogon zu negativen zahlen m <- outer(1:5, 1:4, paste, sep=",") m[2:7] # indizierung auf zugrundeliegendem vektor m[c(4,3), c(1,4,3)] # indizierung nach dimensionen m[1,, drop=FALSE] # dimensionsvereinfachung verhindern select <- rbind( c(1, 1), c(3, 1), c(2, 4) ) m[select] # matrix: jede zeile gibt eine koordinatenkombination an m <- matrix(1:9, nrow=3) which(m %% 2 == 0, arr.ind=F) # gibt koordinaten der TRUE values arrayInd(which(m %% 2 == 0), dim(m)) # wandelt vektor koordinaten in koordinatenmatrix um ## tibbles tb <- tibble(x = 1:3, y = 3:1, z = letters[1:3]) tb[tb$x == 2, ] # wie bei matrizen tb[c("x", "z")] # wie bei listen, gibt spalten x und z tb[tb$x == 2, "z", drop=T] # drop um tibble struktur zu vergessen ## Einzelne Elemente x <- list(bla=1:3, blub=list("fritz", "hans"), 4:6) x[1] # liste mit einem element x[[1]] # element der liste x[[c(2, 1)]] # verschachtelte indizierung x[["blub"]][[1]] # das selbe x <- 1:10 x[[3]] # geht auch fuer atomare vektoren m[[2, 3]] # bei arrays mit entsprechenden dimensionen key <- "bla" x$bla x[[key]] options(warnPartialMatchDollar=T) x$blu # partial matching von "blub" ## Fehlende Indizes x <- 1:3 x[4] # NA x[[4]] # Error lst <- as.list(x) lst[4] # NA lst[[4]] # Error names(x) <- letters[1:3] names(lst) <- letters[1:3] x[["d"]] # Error, ABER: lst[["d"]] # NULL lst$d # NULL ## assignment x <- 1:5 x[c(1, 2, NA)] <- c(1,2) # Error x[c(1, 2, NA)] <- 1 # funktioniert lst <- list(abc=1) lst$ab # partial matching lst$ab <- 2 # kein partial matching, neuer eintrag x <- 1:9 x[1:6] <- 11:13 # recycling x <- 7 x[1e5] <- 5 # funktioniert, out of bound assignment vergroessert vektor tb <- tibble(x=2^(1:4), y=c("1.2", "2.3", "3.4", "4.2")) tb[] <- lapply(tb, as.integer) # erhaelt tibble struktur, weil subsetting assignment lst["abc"] <- NULL # element aus liste entfernen lst["ab"] <- list(NULL) # NULL liste einfügen ## helpers for indexing ### match and merge id <- match(students$grade, info$grade) # find index of entries in students$grade in info$grade cbind(students, info[id, -1]) merge(students, info, by.x="grade", by.y="grade") # kurzform ### sorting x <- runif(100, min=-40, max=52) order(x) # x[order(x)] = sort(x) sort(x) # sortiert rank(x) # ranked alle elemente aufsteigend students[order(students$grade), ] # sortiere nach noten # Missing values ## NULL c() # NULL c(typeof(NULL), length(NULL)) # NULL, 0 ## BASE_TYPE(0) c(typeof(integer(0)), length(integer(0))) ## NA... c(typeof(NA_integer_), length(NA)) # laenge 1 und entsprechenden typ ## indexing x <- 1:3 x[0] x[NULL] x[] x[NA] x[NA_integer_] ## pruefen x <- list(1, NA, NaN, NA_real_, NA_integer_, NA_character_) is.na(x) is.null(x) is.nan(NaN) ## vergleich 1:3 == NA NA == NA identical(NA, NA) NULL == NULL identical(NULL, NULL) # Plots x <- seq(-3, 3, len=100) plot(x, sin(x), type="l", main="Titel", xlab="x-Achse", ylab="y-Achse", xlim=c(-2, 2), ylim=c(-1, 1), lwd=3, lty=2, col="cyan") points(x, sin(x), pch=3, col="red") hist(rnorm(1000)) barplot(1:5) x <- seq(0, 1, len=100) y <- seq(0, pi, len=100) f <- function(x,y) x^2 - sin(y) outer(x,y,f) contour(x,y,outer(x,y,f)) # Randomness ## random deviates set.seed(42) rnorm(8, mean=12, sd=3) rbinom(42, size=13, prob=0.45) runif(10, min=-13, max=26) # Printing print(1:10) cat("3 + 5 ist", 3+5, "\n", sep=" ") paste("3 + 5 ist", 3+5, "\n", sep=" ") as.character(pi) sprintf("Eine Zufallszahl aus der %s ist %.2f.", "Normalverteilung", rnorm(1)) # Listen lst <- list(1:3, "a", c(T, F, T), runif(10), list(1, "b"), sum) identical(lst[[c(5,2)]], "b") str(lst) c(typeof(lst), class(lst), length(lst)) is.vector(lst) is.list(lst) unlist(list(1:10)) as.list(1:10) lst[2] lst[[2]] lst[[2]] <- NULL lst[1] <- list(NULL) str(c(list(), letters[1:5], 42)) # Matrizen n <- 1e3 m1 <- matrix(rnorm(n^2), nrow=n) m2 <- matrix(rnorm(n^2), nrow=n) m <- matrix(1:16, nrow=4, byrow=F) m[2,2] <- 0 m[1,1] <- 0 dim(m) length(m) nrow(m) ncol(m) rowSums(m) colSums(m) det(m) r <- sample(4) y <- solve(m, r) m %*% y solve(m) # inverse qr(m) # qr zerlegung chol(m) # cholesky zerlegung svd(m) # singulaerwertzerlegung eigen(m) # eigenwerte und vektoren m1 + m2 m1 * m2 # elementweise mult m1 %*% m2 # matrix mult t(m1) # transpose aperm(m1) # auf arrays verallgemeinert cbind(m, 1:4) rbind(m, 1:4) ## allgemeiner x <- 1:6 attr(x, "dim") <- c(2,3) dim(x) <- c(2,3,1) array(1:16, dim=c(2,2,2)) matrix(list("a", 1, T, list()), nrow=2, dimnames=list(Zeilen=NULL, Spalten=c("hi", "fritz"))) rownames(x) <- letters[1:2] colnames(x) <- LETTERS[1:3] NROW(1:12) == nrow(matrix(1:12)) # aber nrow(1:12) ist NULL # Tibbles tb <- tibble(x=1:3, y=letters[1:3]) as_tibble %$% list(x=1:3, y=letters[1:3]) as_tibble %$% matrix(1:4, ncol=2, dimnames=list(NULL, c("A", "B"))) add_column(tb, z=-1:1, w = 0) add_row(tb, x=4:5, y=letters[4:5], .before=2) head(tb, 2) tail(tb, 1) dplyr::bind_rows(tb, tibble(x=42:45, y="E")) dplyr::bind_cols(tb, tibble(e=42:44, d="E")) options(tibble.print_min=6, # makes output more compact tibble.print_max=6, tibble.max_etra_cols=0) readr::read_csv("bla.csv") ## dplyr verbes filter(flights, (arr_delay - dep_delay) < -60, dep_delay > 30) # zeilen filtern arrange(flights, desc(dep_delay)) # zeilen sortieren nms <- c("month", "year") select(flights, 5, 5, carrier, "origin", arr_time:flight, -flight, all_of(nms), -any_of(nms)) # position select select(flights, # condition select where(is.character) | contains("delay"), fritz = ends_with("delay"), -starts_with("dep_"), tag = contains("day"), # no regex matches("^y"), # regex everything()) # alle restlichen spalten (umsortierung) rename(flights, monat = month, tag = day) mutate(airlines, name_length = nchar(name), carrier=NULL) transmute(flights, carrier, gain = dep_delay - arr_delay) summarize(flights, total_delay = sum(abs(dep_delay), na.rm = T), delay_range = range(dep_delay, na.rm = T), count_origin = n_distinct(origin)) count(flights, year, month, day, origin, sort=T) # group by variables and summarize n() group_by(flights, month) %>% summarize(delay_mean = mean(dep_delay, na.rm=T)) group_by(flights, month, carrier, dep_time) %>% select(group_cols()) group_by(flights, carrier) %>% filter(sum(distance, na.rm = T) > 5e7) group_by(flights, carrier) %>% mutate(prop = distance / sum(distance, na.rm=T)) summarize(flights, across(where(is.numeric) & !(year | month | day | hour), list(min = min,max = max,mean=mean), na.rm = T, .names = "{fn}.{col}")) mutate(flights, across(dep_time:arr_delay, partial(`*`, 2))) filter(flights, across(where(is.numeric) & !flight & !year, partial(`>`, 700) %.% abs)) rowwise(flights) %>% mutate(m = mean(c_across(dep_time:arr_time)), .keep="used") # vermeiden ## non standard evaluation austricksen tobefiltered <- "dep_delay" filter(flights, .data[[tobefiltered]] > +120) ## join und co. left_join(flights2, airports2, by=c("origin" = "faa")) %>% left_join(airports2, by=c("dest" = "faa"), suffix=c("_origin", "_dest")) right_join(flights2, airports2, by=c("dest" = "faa")) # priorisiert rechts inner_join(flights2, airports2, by=c("dest" = "faa")) # keins na full_join(flights2, airports2, by=c("dest" = "faa")) # egal top_dest <- count(flights, dest, sort=T) %>% head(10) semi_join(flights, top_dest) # only keep rows that join with row in top_dest anti_join(flights, top_dest) # only keep rows that DO NOT join with row in top dest ## tidy tables pivot_longer(table4a, cols=where(is.numeric), names_to="year", values_to="cases") pivot_wider(table2, names_from=type, values_from=count) separate(table3, rate, into = c("cases", "population"), sep = "/", convert = T) extract(table3, rate, into = c("cases", "population"), regex = "^(.*?)/(.*?)$", convert = T) unite(table5, "year", century, year, sep="") %>% mutate(year = as.integer(year)) ## fix missing values complete(stocks, year, qtr) # make implicit missing values explicit drop_na(stocks) replace_na(stocks, list(return = 42)) mutate(stocks, return = na_if(return, "my custom na")) # Apply und co. f <- function(x) sprintf("a random number is %.2f which is %.3f probable", x, pnorm(x)) lapply(rnorm(100), f) sapply(rnorm(100), f) simplify2array(lapply(rnorm(100), f)) replicate(5, runif(10), simplify = TRUE) mapply(`+`, 1:100, 1:100) m <- matrix(1:12, nrow=3) dimnames(m) <- list(cols=NULL, rows=NULL) apply(m, 1, mean) a <- array(1:24, dim=2:4, dimnames=list(x1=NULL, x2=NULL, x3=NULL)) apply(a, c(1,2), range) outer(1:3, 1:5, `*`) funs <- list(mean=mean, median=median, var=var) X <- replicate(5, runif(sample(20, 1)), simplify=F) outer(X, funs, function(x_list, f_list) mapply(function(f, x) f(x), f_list, x_list)) ## mathematische funktionale integrate(sin, 0, pi) optimize(sin, c(0, 2*pi), maximum=T) x <- seq(0, 2*pi, len=10) f_lin <- approxfun(x, sin(x)) f_cub <- splinefun(x, sin(x)) ## funktionsoperatoren fib <- function(n) { if (n<2) return(1) fib(n-2) + fib(n-1) } fib <- memoise::memoise(fib) capture.output(print("hi")) sample2 <- Vectorize(sample, "size") # types sapply(list(NULL, TRUE, # -- basetypes -- 1L, 1, "", raw(3), 1+3i, # ^^ basetypes ^^ list(), expr(a), expr(1+2), expression(1+2), env(), mean, sin, `[`), typeof) %>% unique() sapply(list(NULL, TRUE, # -- basetypes -- 1L, 1, "", raw(3), 1+3i, # ^^ basetypes ^^ list(), expr(a), expr(1+2), expression(1+2), env(), mean, tibble(), # tidyverse data.frame(), matrix(), array(), factor(), Sys.Date(), Sys.time(), proc.time()), class) %>% unique() ## basetypes misc <- list(T, 1:3, pi, "hi", 1+3i, raw(4)) sapply(misc, typeof) is.numeric(c(0.3, 3)) # TRUE is.integer(0.3) # FALSE is.atomic(list()) # FALSE str(list(logical(3), integer(3), double(3), character(3), complex(3), raw(3))) ### integer c(42L, NA_integer_, 1:3, .Machine$integer.max) ### double c(3, 4.2, pi, 1e4, .Machine$xmax, Inf, NaN, NA_real_) c(1/0, -1/0, 0/0, 1/Inf) # numeric synonym zu double in class(), as_numeric(), numeric(n), print(), str() # ABER: is.numeric(1L) # TRUE # Not available type fuer double ist: NA_real_ ### character "bla 'einca' asdf" cat('asdf "as" asdf \\ hi\n') x <- c("ä", "Hallo", "", NA_character_, "\n") nchar(x) cat(r"(\n \ )", "\n") letters LETTERS month.name month.abb ## coercion as.double("2.2") c(TRUE, 15) c(TRUE, 1L, 1, "eins") c(c(TRUE, 1L), 1, "eins") # attributes x <- seq(-2, 3, len=42) attr(x, "asdf") <- c(1,2) structure(x, "asdf" = c(1,2)) attributes(x)$asdf <- c(1,2) # geht auch wenn x <- NULL attr(x, "asdf") attributes(x)$asdf identical(x, seq(-2, 3, len=42)) ## names x <- 1:4 attr(x, "names") <- letters[1:4] names(x) <- letters[1:4] x <- c("a"=1, "b"=2, "c"=3, "d"=4) unname(x) # does not modify x names(x) <- NULL # modifies x # S3 Objekte / Klassen x <- runif(100) attr(x, "class") <- "Toller Hecht" class(x) <- "Toller Hecht" unclass(x) # does not modify x class(x) <- NULL # modifies x ## implizite klassen # falls class nicht gesetzt -> implizite klasse: matrix, array, function, numeric oder typeof(x) ## spezielle S3 Objekte ### factors # integer vektoren mit class = factor und attribut levels x <- sample(13, 100, rep=T) x_factor <- factor(letters[x*2]) typeof(x_factor) # integer class(x_factor) # factor str(attributes(x_factor)) # levels table(x_factor) # summarize factor x_factor[c(3,7), drop = TRUE] # wegwerfen der anderen level ### Date # double vektoren mit class = Date Sys.Date() str(attributes(Sys.Date())) unclass(Sys.Date()) # anzahl tage seit 1.1.1970 ### POSIXct # double vektoren mit class = c(POSIXct, POSIXt) Sys.time() str(attributes(Sys.time())) unclass(Sys.time()) # anzahl sekunden seit 1.1.1970 ### proc_time # double vektor der länge 5 mit class = proc_time proc.time() str(attributes(proc.time())) # in sekunden, genauer: # user: beanspruchung der CPU durch R # system: beanspruchung der CPU durch betriebssystem im auftrag von R # elapsed: gesamt str(unclass(proc.time())) system.time({ data <- runif(1e6) stats <- c(mean(data), var(data)) }) # gibt auch proc_time objekt ### tibble # data.frame mit class = c(tbl_df, tbl). ein data.frame ist wiederum eine liste seiner spalten tb <- tibble(x = 1:3, y = letters[3:1]) str(attributes(tb)) typeof(tb) str(unclass(tb)) tb$x tb$z <- matrix(1:15, nrow=3) # kann matrizen tb$e <- tibble(a=sample(100, 3), b=rnorm(3)) # und tibbles as_tibble(as.matrix(tb[,-2])) # funktionen ## pipe pi %>% `*`(2,.) %>% sin %>% abs %>% `<`(., 1e-14) -> res ## eigene operatoren `%.%` <- function(f, g) function(...) f(g(...)) `%$%` <- function(f, x) f(x) flip <- function(f) function(x, y) f(y, x) str %.% attributes %$% tibble (a = sample(100, 3), b = rnorm(3)) ## ersetzungsfunktionen `modify<-` <- function(x, position, value) { x[position] <- value x } x <- 1:10 modify(x, 3) <- 42L modify(names(x), 3) <- "hi" ## do.call do.call(mean, list(x=sample(100), na.rm=T)) ## funktionstypen # funktionen haben class = function, aber c(typeof(mean), typeof(sum), typeof(`[`)) # builtin und special sind primitive, alle anderen closure f <- function(x) { # kommentar 10 } formals(f) body(f) environment(f) formals(args(sum)) # argument liste primitiver funktionen ## lazy evaluation f(stop("hi")) # argument wird nicht ausgewertet ## default arguments f <- function(x = y*2, y = 1, z = a+b, e=ls()) { # defaults refer to other arguments a <- 10 b <- 100 print(e) cat(sprintf("missing x: %s, missing y: %s\n", missing(x), missing(y))) c(x, y, z) } f(x=3, e=ls()) # ls is evaluated in caller env f() # ls is evaluated in execution env ## dot-dot-dot f <- function(x, ...) { # potentially infinite additional parameters str(list(first = ..1, third = ..3)) # access additional parameters mean(unlist(list(...))) } f(1,2,3,42) ## return values invisible(42) # hide return value f <- function(x) { print("Hello") on.exit(print(1)) on.exit(print(2), add = TRUE, after = FALSE) if (x) return(10) else stop("Error") print("Well...") } f(TRUE) f(FALSE) # strings ## basics str_length(c("a", "abs", NA, "")) str_c("prefix", LETTERS[sample(26, 3, rep=T)], "suffix", # vektorisiert in allen args sep = "-", collapse = " * ") x <- "Fritz Walter ist toll. " str_sub(x, start=3, end=5) str_sub(x, end=-5) # only drop last 4 chars str_sub(x, start=7, end=12) <- "Hasl" str_trim(x) # remove whitespace in beginning and end str_squish(x) # remove repeated whitespace inside str_to_upper(x) str_to_lower(x) str_to_title(x) str_dup(c("a", "bB"), 2:5) ## formatting str_glue("3 * pi = {format(3*pi, digits=3)} und das ist {'{toll}'}, weil {x}.") # auch vektorisiert str_glue_data(tibble(x=1:3, y=letters[1:3]), "Es ist x={x} <-> y={y}") ## regex str_detect(x, "[aeiou]") # alles vektorisiert str_subset(letters, "[aeiou]") # recycling str_which(letters, "[aeiou]") str_count(letters, letters[1:13]) str_extract(c(x, "Stopp"), ".t") str_extract_all(c(x, "Stopp"), ".t") str_locate(c(x, "Stopp"), ".t") str_locate_all(c(x, "Stopp"), ".t") str_split(c("Kaffee,Klettern_Sport.Wald", "Bla.Furz"), "[,_\\.]") str_split("Fritz.kann.kochen", fixed(".K", ignore_case=T)) str_match("Fritz.kann.kochen", "(.)\\.(.)") # gibt gruppen aus str_match_all("Fritz.kann.kochen", "(.)\\.(.)") str_replace("Fritz.kann.kochen", "(.)\\.(.)", "\\2$\\1") # use \1.. in replacement to access groups str_replace_all("Fritz.kann.kochen", "(.)\\.(.)", "\\2$\\1") # environments e1 <- env(a = FALSE, b = "a", c = 2.3, d = 1:3) c(class(e1), typeof(e1), length(e1)) e2 <- e1 # environments werden per referenz uebergeben e2$a = T e1$a identical(e1, e2) e1$d <- e1 ## env infos env_print(e1) env_names(e1) env_names(empty_env()) # leere umgebung env_names(global_env()) # globale umgebung (konsole) env_label(global_env()) env_label(e1) env_label(current_env()) # aktuelle umgebung e2 <- env(e1, z = 42) env_parent(e1) env_parent(e2) env_parent(empty_env()) # error, leere umgebung hat kein parent env_parents(e1) env_parents(e2) ## manipulate environments env_bind(e1, z=69, asdf="fritz") env_get(e2, "b", inherit=T) # inherit = T search in parent env if not found here str(env_get_list(e1, c("a", "b", "d"))) env_has(e2, c("b", "z"), inherit=F) env_unbind(e2, c("b", "z")) ## function environments a <- 42 f <- function(x) x + a env_label(current_env()) env_label(fn_env(f)) fn_env(f) <- e1 g <- function(x) { print(env_label(current_env())) function(y) x+y } h <- g(3) ## lazy evaluation x <- 1 y <- 2 env_bind_lazy(current_env(), v = c(x,y)) # creates promise v pointing to c(x,y) x <- 5 v # OOP # sloop inspectino otype(tibble(x=1:3, y=3:1)) s3_class(matrix(1:10, nrow=2)) ftype(print) ftype(apply) s3_methods_generic("elapse_time") inherits(tibble(x=1:12), "tbl_df") s3_dispatch(print(tibble(x=1:12))) ## S3 tyrion <- structure(list(name = "Tyrion", age = 42), class = c("Noble", "Person")) print.Person <- function(p) cat(str_glue("{p$name} ({p$age})"), "\n") elapse_time <- function(obj, time) { # generic function UseMethod("elapse_time") } elapse_time.Person <- function(p, time) { # implementierte methode p$age <- p$age + time p } elapse_time.default <- function(o, t) { stop(str_glue("Cannot elapse time for object of class {class(o)}.")) } elapse_time.Noble <- function(p, time) { cat("A noble person does not age\n") # actually wrong NextMethod() # go to next method in dispatch list } ### constructor helpers new_Person <- function(x, subclass = NULL) { stopifnot("Person must be a list" = is.list(x)) structure(x, class=c(subclass, "Person")) } new_Noble <- function(x, subclass = NULL) { new_Person(x, subclass=c(subclass, "Noble")) } validate_Person <- function(p) { if(!inherits(p, "Person")) stop("Object must inherit Person") invisible(p) } Person <- function(name, age) { p <- new_Person(list(name = name, age = age)) validate_Person(p) p } ### coercion as_Person <- function(x, ...) UseMethod("as_Person") as_Person.Person <- `(` as_Person.character <- function(name, age) Person(name, age) ### internal generics c.numeric <- function(...) return("c: numeric") c(42) # does not use c.numeric because class is implicit c(structure(42, class="numeric")) ### group generics units <- c(kg = 1, g = 1e-3, mg = 1e-6) m <- structure(1:12, class="Mass", unit="kg") m2 <- structure(350, class="Mass", unit="g") print.Mass <- function(x) print(str_glue("{x} {attr(x, 'unit')}")) Summary.Mass <- function(x, ...) { m <- NextMethod() structure(m, class="Mass", unit=attr(x, "unit")) } ### double dispatch `+.Person` <- function(x, y) print("+ on person") `+.Mass` <- function(x, y) { a <- unclass(x) b <- unclass(y) structure(a * units[[attr(x, "unit")]] + b * units[[attr(y, "unit")]], class="Mass", unit = "kg") } m + m2 m + tyrion # error because of incompatible methods m + 1 # error in +.Mass ## S4 .Person <- setClass("Person", slots = c(name = "character", age = "numeric")) .Employee <- setClass("Employee", contains = "Person", slots = c(boss = "Person")) Person <- function(name, age) { stopifnot(length(name) == 1 && length(age)) .Person(name = name, age = age) } Employee <- function(name, age, boss) { p <- Person(name = name, age = age) .Employee(p, boss = boss) } tyrion <- Person(name = "Tyrion", age = 42) peter <- Employee(name = "Peter", age = 13, boss = tyrion) tyrion@age <- tyrion@age + 1 tyrion@name <- 42 slotNames(tyrion) is(tyrion) is(peter, "Person") typeof(tyrion) class(tyrion) ### generics setGeneric("laugh", function(x) standardGeneric("laugh")) setMethod("laugh", "Person", function(x) { cat(str_glue("{x@name} laughs out loudly."), "\n") }) setMethod("laugh", "Employee", function(x) { cat(str_glue("{x@name} laughs out shyly."), "\n") }) formals(getGeneric("show")) setMethod("show", "Person", function(object) { cat(str_glue("{object@name} ({object@age})"), "\n") }) setMethod("show", "Employee", function(object) { cat(str_glue("{object@name} ({object@age}) an Employee"), "\n") }) ### getters and setters setGeneric("name", function(x) standardGeneric("name")) setMethod("name", "Person", function(x) x@name) setGeneric("name<-", function(x, value) standardGeneric("name<-")) setMethod("name<-", "Person", function(x, value) { x@name <- value x }) ### coercion as(peter, "Person") # default emp <- as(tyrion, "Employee") # default emp@boss setAs("Person", "Employee", function(from) { stop("Can not coerce a Person to an Employee") }) ### introspection s4_methods_generic("laugh") s4_methods_class("Person") ### inheriting S3 classes RangedNumeric <- setClass( "RangedNumeric", contains = "numeric", slots = c(min = "numeric", max = "numeric") ) rn <- RangedNumeric(1:10, min=1, max=10) rn@.Data # access underlying S3 or implicite class # meta programming ## basics x <- expr(3+5*y) y <- 12 eval(x) str(exprs(name = 1+2, f(x), sin(42))) typeof(expr(1)) c(typeof(expr(variable)), class(expr(variable))) # symbol name c(typeof(expr(sin(x))), class(expr(sin(x)))) # language call lobstr::ast(f(g(x, h(), 1), x)) a <- expr(f(g(x, h(), 1), x)) str(as.list(a)) a[[2]][[3]][[1]] a[[c(2,3,1)]] <- expr(q) expr_text(a) identical(parse_expr("f(g(x, q(), 1), x)"), a) parse_exprs("x <- 4\nprint(x+5)") # parse multiple expressions as expr list ## arguments f <- function(x, ...) { print(enexpr(x)) # does not use promise force(x) print(enexpr(x)) enexprs(...) } f(3+4, sin(pi), 0/0) ## evaluation x <- y <- 3 eval(expr(x+y), envir = env(`+` = `-`)) ## quosures f <- function() { x <- 10 quo(1+x) } x <- 100 q <- f() eval(quo_get_expr(q)) # evaluates in current environment eval_tidy(q) # evaluates in captured environment expr_text(quo(1+2)) # prints weird ~ in front quo_text(quo(1+2)) foo <- function(x, y) { z <- 1 x <- enexpr(x) y <- enquo(y) c(eval(x), eval_tidy(y)) } z <- 100 foo(z*2, z*2) # 2 200 (x is evaluated in execution env of foo, y is evaluated in global env) eval_tidy(expr(a <- 42)) # does not actually assign eval(expr(a <- 42)) # does assign x <- 1 eval_tidy(quo(x+y), list(y = 100)) ## tidy eval framework ### bang bang a <- expr(f(x)) expr(1 + !!a) # Bang-Bang Operator lobstr::ast(expr(f(x))) lobstr::ast(!!expr(f(x))) ### big bang x <- 1:3 str(exprs(!!x)) str(exprs(!!!x)) # big bang ### walrus name <- "asdf" tibble(!!name := 1:5) ### pronouns y <- 4 tb <- tibble(x = 1:3, y=3:1) eval_tidy(quo(.data$y + .env$y*10), tb) # .data datamask, .env environment ### geschachtelte quosures x <- 1 lobstr::ast(7 + !!(x+2)) lobstr::ast(7 + !!quo(x+2)) # unwraps quosure and puts it in main quosure y <- 1 create_q <- function() { y <- 10 quo(y) } qu <- create_q() eval_tidy(quo(y + !!qu)) ## applications var <- quo(y) tb <- tibble(x=1:5, y = 5:1) eval_tidy(quo(!!var == 4), tb) var <- "y" eval_tidy(quo(.data[[var]] == 4), tb) mysummarise <- function(tb, mean_var, ...) { qg <- enquos(...) qm <- enquo(mean_var) mean_name <- str_glue("mean_{quo_name(qm)}") tb %>% group_by(!!!qg) %>% summarise(!!mean_name := mean(!!qm)) } filterx <- function(var) { filter2(tb, {{var}} == 1) } source2 <- function(path) { l <- readLines(path) exs <- lapply(l, parse_expr) model <- env(base_env()) for (ex in exs) res <- eval_tidy(ex, model) res } source2("test.R") namedlist <- function(...) { qs <- enquos(...) l <- list(...) names(l) <- lapply(qs, quo_text) l } namedlist(letters, 1:10) filter2 <- function(tb, ex) { ex <- enquo(ex) tb[eval_tidy(ex, tb), ] } mkInfix <- function(f) { f <- enexpr(f) e <- caller_env() name <- expr_text(f) e[[str_glue("%{name}%")]] <- eval(f) } `%->%` <- function(x, y) { y <- enexpr(y) e <- caller_env() e[[expr_text(y)]] <- x invisible(x) } # conditions stop("Error!") stopifnot("Error!" = 3 == 4) warning("Warning!") warnings() last.warning message("Message!") { try(stop("hi"), silent = FALSE) print("hallo") } suppressWarnings({warning("warn"); message("msg")}) suppressMessages({warning("warn"); message("msg")}) tryCatch( message = msgHandler, warning = warnHandler, error = errorHandler, expr = { } ) withCallingHandlers( message = msgHandler, warning = warnHandler, error = errorHandler, expr = { } ) outerVec <- function(a, b, f) { res <- array(0, c(length(a), length(b), length(f(a[[1]], b[[1]])))) for (i in seq_along(a)) { for (j in seq_along(b)) { res[i,j,] <- f(a[[i]], b[[j]]) } } res } outerVec(1:5, 1:5, function(x, y) c(x,y)) cons <- function(h, t) function(x) if(x) h else t `%:%` <- function(h, t) cons(h, t) itake <- function(x, k) { res <- c() for (i in 1:k) { res <- c(res, ihead(x)) x <- itail(x) } res } itail <- function(x) if(is.null(x)) NULL else x(F) ihead <- function(x) if(is.null(x)) NULL else x(T) nocons <- function(t) function(x) if(x) ihead(t) else itail(t) izip <- function(f, x, y) { cons(f(ihead(x), ihead(y)), izip(f, itail(x), itail(y))) } iapply <- function(x, f) f(ihead(x)) %:% iapply(itail(x), f) ifilter <- function(x, pred) { if(pred(ihead(x))) { ihead(x) %:% ifilter(itail(x), pred) } else { nocons(ifilter(itail(x), pred)) } } ifold <- function(xs, f, acc) { ifold(itail(xs), f, f(ihead(xs), acc)) } fibs <- 1 %:% (1 %:% izip(`+`, fibs, itail(fibs))) seq1 <- cons(1, seq1) ns <- 1 %:% izip(`+`, seq1, ns) iseq <- function(a, b=NULL, by=1) { d <- cons(by, d) res <- a %:% izip(`+`, d, res) if (is.null(b)) return(res) ifilter(res, function(x) x <= b) } mk_primes <- function(xs) { ihead(xs) %:% mk_primes(ifilter(itail(xs), function(a) a %% ihead(xs) != 0)) } primes <- mk_primes(itail(ns))