--- name: base-r description: Предоставляет рекомендации по программированию на базовом R, охватывающие структуры данных, обработку данных, статистическое моделирование, визуализацию и ввод-вывод, с использованием только пакетов, включённых в стандартную установку R --- # Навык программирования на базовом R Полный справочник по программированию на базовом R — структуры данных, управление потоком выполнения, функции, ввод-вывод, статистические вычисления и построение графиков. ## Краткий справочник ### Структуры данных ```r # Vectors (atomic) x <- c(1, 2, 3) # numeric y <- c("a", "b", "c") # character z <- c(TRUE, FALSE, TRUE) # logical # Factor f <- factor(c("low", "med", "high"), levels = c("low", "med", "high"), ordered = TRUE) # Matrix m <- matrix(1:6, nrow = 2, ncol = 3) m[1, ] # first row m[, 2] # second column # List lst <- list(name = "ali", scores = c(90, 85), passed = TRUE) lst$name # access by name lst[[2]] # access by position # Data frame df <- data.frame( id = 1:3, name = c("a", "b", "c"), value = c(10.5, 20.3, 30.1), stringsAsFactors = FALSE ) df[df$value > 15, ] # filter rows df$new_col <- df$value * 2 # add column ``` ### Выбор подмножеств ```r # Vectors x[1:3] # by position x[c(TRUE, FALSE)] # by logical x[x > 5] # by condition x[-1] # exclude first # Data frames df[1:5, ] # first 5 rows df[, c("name", "value")] # select columns df[df$value > 10, "name"] # filter + select subset(df, value > 10, select = c(name, value)) # which() for index positions idx <- which(df$value == max(df$value)) ``` ### Управление потоком выполнения ```r # if/else if (x > 0) { "positive" } else if (x == 0) { "zero" } else { "negative" } # ifelse (vectorized) ifelse(x > 0, "pos", "neg") # for loop for (i in seq_along(x)) { cat(i, x[i], "\n") } # while while (condition) { # body if (stop_cond) break } # switch switch(type, "a" = do_a(), "b" = do_b(), stop("Unknown type") ) ``` ### Функции ```r # Define my_func <- function(x, y = 1, ...) { result <- x + y return(result) # or just: result } # Anonymous functions sapply(1:5, function(x) x^2) # R 4.1+ shorthand: sapply(1:5, \(x) x^2) # Useful: do.call for calling with a list of args do.call(paste, list("a", "b", sep = "-")) ``` ### Семейство Apply ```r # sapply — simplify result to vector/matrix sapply(lst, length) # lapply — always returns list lapply(lst, function(x) x[1]) # vapply — like sapply but with type safety vapply(lst, length, integer(1)) # apply — over matrix margins (1=rows, 2=cols) apply(m, 2, sum) # tapply — apply by groups tapply(df$value, df$group, mean) # mapply — multivariate mapply(function(x, y) x + y, 1:3, 4:6) # aggregate — like tapply for data frames aggregate(value ~ group, data = df, FUN = mean) ``` ### Операции со строками ```r paste("a", "b", sep = "-") # "a-b" paste0("x", 1:3) # "x1" "x2" "x3" sprintf("%.2f%%", 3.14159) # "3.14%" nchar("hello") # 5 substr("hello", 1, 3) # "hel" gsub("old", "new", text) # replace all grep("pattern", x) # indices of matches grepl("pattern", x) # logical vector strsplit("a,b,c", ",") # list("a","b","c") trimws(" hi ") # "hi" tolower("ABC") # "abc" ``` ### Ввод-вывод данных ```r # CSV df <- read.csv("data.csv", stringsAsFactors = FALSE) write.csv(df, "output.csv", row.names = FALSE) # Tab-delimited df <- read.delim("data.tsv") # General df <- read.table("data.txt", header = TRUE, sep = "\t") # RDS (single R object, preserves types) saveRDS(obj, "data.rds") obj <- readRDS("data.rds") # RData (multiple objects) save(df1, df2, file = "data.RData") load("data.RData") # Connections con <- file("big.csv", "r") chunk <- readLines(con, n = 100) close(con) ``` ### Базовое построение графиков ```r # Scatter plot(x, y, main = "Title", xlab = "X", ylab = "Y", pch = 19, col = "steelblue", cex = 1.2) # Line plot(x, y, type = "l", lwd = 2, col = "red") lines(x, y2, col = "blue", lty = 2) # add line # Bar barplot(table(df$category), main = "Counts", col = "lightblue", las = 2) # Histogram hist(x, breaks = 30, col = "grey80", main = "Distribution", xlab = "Value") # Box plot boxplot(value ~ group, data = df, col = "lightyellow", main = "By Group") # Multiple plots par(mfrow = c(2, 2)) # 2x2 grid # ... four plots ... par(mfrow = c(1, 1)) # reset # Save to file png("plot.png", width = 800, height = 600) plot(x, y) dev.off() # Add elements legend("topright", legend = c("A", "B"), col = c("red", "blue"), lty = 1) abline(h = 0, lty = 2, col = "grey") text(x, y, labels = names, pos = 3, cex = 0.8) ``` ### Статистика ```r # Descriptive mean(x); median(x); sd(x); var(x) quantile(x, probs = c(0.25, 0.5, 0.75)) summary(df) cor(x, y) table(df$category) # frequency table # Linear model fit <- lm(y ~ x1 + x2, data = df) summary(fit) coef(fit) predict(fit, newdata = new_df) confint(fit) # t-test t.test(x, y) # two-sample t.test(x, mu = 0) # one-sample t.test(before, after, paired = TRUE) # Chi-square chisq.test(table(df$a, df$b)) # ANOVA fit <- aov(value ~ group, data = df) summary(fit) TukeyHSD(fit) # Correlation test cor.test(x, y, method = "pearson") ``` ### Преобразование данных ```r # Merge (join) merged <- merge(df1, df2, by = "id") # inner merged <- merge(df1, df2, by = "id", all = TRUE) # full outer merged <- merge(df1, df2, by = "id", all.x = TRUE) # left # Reshape wide <- reshape(long, direction = "wide", idvar = "id", timevar = "time", v.names = "value") long <- reshape(wide, direction = "long", varying = list(c("v1", "v2")), v.names = "value") # Sort df[order(df$value), ] # ascending df[order(-df$value), ] # descending df[order(df$group, -df$value), ] # multi-column # Remove duplicates df[!duplicated(df), ] df[!duplicated(df$id), ] # Stack / combine rbind(df1, df2) # stack rows (same columns) cbind(df1, df2) # bind columns (same rows) # Transform columns df$log_val <- log(df$value) df$category <- cut(df$value, breaks = c(0, 10, 20, Inf), labels = c("low", "med", "high")) ``` ### Окружение и отладка ```r ls() # list objects rm(x) # remove object rm(list = ls()) # clear all str(obj) # structure class(obj) # class typeof(obj) # internal type is.na(x) # check NA complete.cases(df) # rows without NA traceback() # after error debug(my_func) # step through browser() # breakpoint in code system.time(expr) # timing Sys.time() # current time ``` ## Справочные файлы Для более глубокого изучения читайте справочные файлы в `references/`: ### Подводные камни функций и краткий справочник (сокращённое изложение справочного руководства R 4.5.3) Неочевидное поведение, неожиданные значения по умолчанию и сложные взаимодействия — только то, чего Claude ещё не знает: - **data-wrangling.md** — Читать, когда: выбор подмножества возвращает неверный тип, apply для фрейма данных вызывает неожиданное приведение типов, merge/split/cbind ведут себя необычно, уровни факторов сохраняются после фильтрации, возникают крайние случаи table/duplicated. - **modeling.md** — Читать, когда: непонятен синтаксис формул (`I()`, `*` в сравнении с `:`, `/`), aov выдаёт неверный тип SS, glm незаметно строит модель OLS, nls не сходится, predict возвращает неверную шкалу, optim/optimize требует настройки. - **statistics.md** — Читать, когда: проверка гипотез даёт неожиданный результат, нужно выбрать правильный метод p.adjust, параметры кластеризации кажутся неверными, непонятны имена функций распределений (префиксы `d`/`p`/`q`/`r`). - **visualization.md** — Читать, когда: настройки par неожиданно сбрасываются, непонятно взаимодействие layout/mfrow, подписи осей обрезаются, цвета выглядят неправильно, нужны специальные графики (contour, persp, mosaic, pairs). - **io-and-text.md** — Читать, когда: read.table незаметно отбрасывает данные или неправильно разбирает столбцы, регулярные выражения ведут себя не так, как ожидалось, форматирование sprintf вызывает сложности, вывод write.table содержит нежелательные имена строк. - **dates-and-system.md** — Читать, когда: преобразование Date/POSIXct даёт неправильный день, часовые пояса вызывают сдвиг на единицу, единицы difftime неожиданны, нужно программно искать/перечислять/проверять файлы. - **misc-utilities.md** — Читать, когда: do.call ведёт себя иначе, чем прямой вызов, нужны Reduce/Filter/Map, обработчик tryCatch не срабатывает, all.equal возвращает строку вместо логического значения, требуется настройка функций временных рядов. ## Советы по написанию хорошего кода R - Используйте `vapply()` вместо `sapply()` в промышленном коде — она обеспечивает соблюдение типов возвращаемых значений - Предпочитайте `seq_along(x)` вместо `1:length(x)` — последний вариант ломается при пустом `x` - Используйте `stringsAsFactors = FALSE` в `read.csv()` / `data.frame()` (значение по умолчанию изменилось в R 4.0) - По возможности векторизуйте операции вместо написания циклов - Используйте `stop()`, `warning()`, `message()` для обработки ошибок — не `print()` - `<<-` выполняет присваивание в родительском окружении — используйте редко и осознанно - `with(df, expr)` позволяет не повторять повсюду `df$` - `Sys.setenv()` и `.Renviron` — для переменных окружения