# Базовый R

## Файл: SKILL.md

---
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` — для переменных окружения


## Файл: references/misc-utilities.md


# Разные утилиты — краткий справочник

> Неочевидное поведение, подводные камни и непростые значения по умолчанию функций R.
> Только то, чего Claude ещё не знает.

---

## do.call

- `do.call(fun, args_list)` — `args` должен быть **списком**, даже для одного аргумента.
- `quote = TRUE` предотвращает вычисление аргументов перед вызовом — необходимо при передаче выражений/символов.
- Поведение `substitute` внутри `do.call` отличается от прямых вызовов. Семантика для этого случая определена не полностью.
- Полезный шаблон: `do.call(rbind, list_of_dfs)` для объединения списка фреймов данных.

---

## Reduce / Filter / Map / Find / Position

Вспомогательные функции функционального программирования из базового R — действительно неочевидные.

- `Reduce(f, x)` применяет бинарную функцию `f` накопительно: `Reduce("+", 1:4)` = `((1+2)+3)+4`. Направление важно для некоммутативных операций.
- `Reduce(f, x, accumulate = TRUE)` возвращает все промежуточные результаты — эквивалент Python `itertools.accumulate`.
- `Reduce(f, x, right = TRUE)` выполняет свёртку справа: `f(x1, f(x2, f(x3, x4)))`.
- `Reduce` с `init` добавляет начальное значение: `Reduce(f, x, init = v)` = `f(f(f(v, x1), x2), x3)`.
- `Filter(f, x)` сохраняет элементы, для которых `f(elem)` равно `TRUE`. В отличие от `x[sapply(x, f)]`, корректно обрабатывает `NULL`/пустые значения.
- `Map(f, ...)` — простая обёртка над `mapply(f, ..., SIMPLIFY = FALSE)` — всегда возвращает список.
- `Find(f, x)` возвращает **первый** элемент, для которого `f(elem)` равно `TRUE`. `Find(f, x, right = TRUE)` — для последнего.
- `Position(f, x)` возвращает **индекс** первого совпадения (как `Find`, но возвращает позицию, а не значение).

---

## lengths

- `lengths(x)` возвращает длину **каждого элемента** списка. Эквивалентна `sapply(x, length)`, но быстрее (реализована на C).
- Работает с любым спископодобным объектом. Возвращает целочисленный вектор.

---

## conditions (tryCatch / withCallingHandlers)

- `tryCatch` **разматывает** стек вызовов — обработчик выполняется в вызывающем окружении, а не там, где возникла ошибка. Возобновить выполнение невозможно.
- `withCallingHandlers` НЕ разматывает стек — обработчик выполняется там, где было сигнализировано условие. Можно исследовать/записать его в журнал, а затем позволить условию распространяться дальше.
- `tryCatch(expr, error = function(e) e)` возвращает объект условия ошибки.
- `tryCatch(expr, warning = function(w) {...})` перехватывает **первое** предупреждение и завершает выполнение. Используйте `withCallingHandlers` + `invokeRestart("muffleWarning")`, чтобы подавить предупреждения, но продолжить работу.
- Блок `finally` в `tryCatch` выполняется всегда (как try/finally в Java).
- `globalCallingHandlers()` регистрирует обработчики, действующие в течение сессии (полезно для журналирования).
- Пользовательские условия: `stop(errorCondition("msg", class = "myError"))`, затем перехват через `tryCatch(..., myError = function(e) ...)`.

---

## all.equal

- Проверяет **приблизительное равенство** с допуском (по умолчанию `1.5e-8`, то есть `sqrt(.Machine$double.eps)`).
- Возвращает `TRUE` или **символьную строку**, описывающую различие, — НЕ `FALSE`. В условиях используйте `isTRUE(all.equal(x, y))`.
- Аргумент `tolerance` управляет числовым допуском. `scale` — абсолютным или относительным сравнением.
- Проверяет атрибуты, имена, размерности — тщательнее, чем `==`.

---

## combn

- `combn(n, m)` или `combn(x, m)`: генерирует все сочетания из `m` элементов `x`.
- Возвращает **матрицу** с `m` строками; каждый столбец — одно сочетание.
- Аргумент `FUN` применяет функцию к каждому сочетанию: `combn(5, 3, sum)` возвращает суммы всех подмножеств из 3 элементов.
- `simplify = FALSE` возвращает список вместо матрицы.

---

## modifyList

- `modifyList(x, val)` заменяет элементы списка `x` элементами из `val` по **имени**.
- Установка значения `NULL` **удаляет** соответствующий элемент из списка.
- **Действительно** добавляет новые имена, отсутствующие в `x`, — внутри используется `x[names(val)] <- val`, поэтому любое имя из `val` добавляется или заменяется.

---

## relist

- Обратная операция к `unlist`: по плоскому вектору и списку-скелету восстанавливает вложенную структуру.
- `relist(flesh, skeleton)` — `flesh` содержит плоские данные, `skeleton` задаёт форму.
- Работает с факторами, матрицами и вложенными списками.

---

## txtProgressBar

- `txtProgressBar(min, max, style = 3)` — стиль 3 показывает процент + полосу (самый полезный).
- Обновление через `setTxtProgressBar(pb, value)`. Закрытие через `close(pb)`.
- Стиль 1: вращающийся `|/-\`, стиль 2: простой индикатор прогресса. Только стиль 3 показывает процент.

---

## object.size

- Возвращает **оценку** памяти, занимаемой объектом. Не всегда точна при общих ссылках.
- `format(object.size(x), units = "MB")` — для человекочитаемого вывода.
- Не учитывает размер окружений или внешних указателей.

---

## installed.packages / update.packages

- `installed.packages()` может работать медленно (сканирует все пакеты). Используйте `find.package()` или `requireNamespace()` для проверки конкретного пакета.
- `update.packages(ask = FALSE)` обновляет все пакеты без запросов.
- `lib.loc` указывает, какую библиотеку проверять/обновлять.

---

## vignette / demo

- `vignette()` перечисляет все виньетки; `vignette("name", package = "pkg")` открывает конкретную.
- `demo()` перечисляет все демонстрации; `demo("topic")` запускает одну интерактивно.
- `browseVignettes()` открывает HTML-браузер виньеток.

---

## Временные ряды: acf / arima / ts / stl / decompose

- `ts(data, start, frequency)`: `frequency` — число наблюдений на единицу времени (12 для ежемесячных, 4 для ежеквартальных).
- Для `acf` по умолчанию `type = "correlation"`. Используйте `type = "partial"` для PACF. `plot = FALSE` подавляет автоматическое построение графика.
- `arima(x, order = c(p,d,q))` — для моделей ARIMA. `seasonal = list(order = c(P,D,Q), period = S)` — для сезонной компоненты.
- `arima` обрабатывает значения `NA` во временном ряду (через фильтр Калмана).
- `stl` требует `s.window` (сезонное окно) — его нужно указать, значения по умолчанию нет. `s.window = "periodic"` предполагает фиксированную сезонность.
- `decompose`: проще, чем `stl`, использует скользящие средние. `type = "additive"` или `"multiplicative"`.
- Компоненты результата `stl`: матрица `$time.series` со столбцами `seasonal`, `trend`, `remainder`.


## Файл: references/data-wrangling.md


# Обработка данных — краткий справочник

> Неочевидное поведение, подводные камни и непростые значения по умолчанию функций R.
> Только то, чего Claude ещё не знает.

---

## Extract / Extract.data.frame

Подводные камни индексации в базовом R.

- `m[j = 2, i = 1]` — это `m[2, 1]`, а не `m[1, 2]`: имена аргументов **игнорируются** в `[`, сопоставление только позиционное. Никогда не именуйте аргументы индексов.
- Индексация фактором: `x[f]` использует целочисленные коды фактора `f`, а не его символьные метки. Используйте `x[as.character(f)]` для индексации по меткам.
- `x[[]]` без индекса всегда вызывает ошибку. `x$name` по умолчанию выполняет частичное сопоставление; `x[["name"]]` — нет (по умолчанию точное).
- Присваивание `NULL` через `x[[i]] <- NULL` или `x$name <- NULL` **удаляет** этот элемент списка.
- `[` для фрейма данных с одним столбцом: `df[, 1]` возвращает **вектор** (по умолчанию drop=TRUE для столбцов), но `df[1, ]` возвращает **фрейм данных** (drop=FALSE для строк). Явно задавайте `drop = FALSE`.
- Индексация фрейма данных матрицей (`df[cbind(i,j)]`) сначала преобразует его в матрицу — избегайте этого.

---

## subset

Используйте только интерактивно; небезопасна для программирования.

- Аргумент `subset` использует **нестандартное вычисление** — имена столбцов разрешаются во фрейме данных, что при программном использовании может незаметно подхватить не те переменные. В функциях используйте `[` с явной логикой.
- `NA` в логическом условии трактуются как `FALSE` (строки незаметно отбрасываются).
- После выбора подмножества факторы могут сохранять неиспользуемые уровни; вызывайте `droplevels()`.

---

## match / %in%

- `%in%` **никогда не возвращает NA** — поэтому, в отличие от `==`, безопасен для условий `if()`.
- `match()` возвращает только позицию **первого** совпадения; повторы в `table` игнорируются.
- Факторы, векторы raw и списки перед сопоставлением преобразуются в символьный вид.
- `NaN` совпадает с `NaN`, но не с `NA`; `NA` совпадает только с `NA`.

---

## apply

- Для **фрейма данных** `apply` сначала выполняет преобразование в матрицу через `as.matrix` — смешанные типы становятся символьными.
- Ориентация возвращаемого значения транспонирована: если FUN возвращает вектор длины n, размерность результата — `c(n, dim(X)[MARGIN])`. Результаты для строк становятся **столбцами**.
- Результаты-факторы в выходном массиве преобразуются в символьные значения.
- Имена аргументов `...` не должны совпадать с `X`, `MARGIN` или `FUN` (риск частичного сопоставления).

---

## lapply / sapply / vapply

- `sapply` может непредсказуемо вернуть вектор, матрицу или список — в неинтерактивном коде используйте `vapply` с явным шаблоном `FUN.VALUE`.
- Прямой вызов примитивов в `lapply` может вызвать проблемы диспетчеризации; оборачивайте в `function(x) is.numeric(x)`, а не передавайте просто `is.numeric`.
- `sapply` с `simplify = "array"` может создавать массивы большей размерности (не только матрицы).

---

## tapply

- Возвращает **массив** (не фрейм данных). Информация о классе возвращаемых значений **отбрасывается** (например, объекты Date становятся числовыми).
- Аргументы `...` для FUN **не** разделяются по ячейкам — они применяются глобально, поэтому FUN не должна ожидать дополнительные аргументы той же длины, что и X.
- `default = NA` заполняет пустые ячейки; задавайте `default = 0` для операций наподобие суммирования. До R 3.4.0 значение было жёстко задано как `NA`.
- Используйте `array2DF()` для преобразования результата во фрейм данных.

---

## mapply

- Имя аргумента — `SIMPLIFY` (все буквы заглавные), а не `simplify` — несогласованность с `sapply`.
- `MoreArgs` должен быть **списком** аргументов, по которым не выполняется векторизация.
- Повторяет более короткие аргументы до общей длины; аргумент нулевой длины даёт результат нулевой длины.

---

## merge

- По умолчанию `by` равен `intersect(names(x), names(y))` — объединение может незаметно выполняться по непредусмотренным столбцам, если во фреймах данных совпадают имена столбцов.
- `by = 0` или `by = "row.names"` объединяет по именам строк, добавляя столбец "Row.names".
- `by = NULL` (или оба `by.x`/`by.y` длины 0) создаёт **декартово произведение**.
- Результат по умолчанию сортируется по столбцам `by` (`sort = TRUE`). Для несортированного вывода используйте `sort = FALSE`.
- Совпадения повторяющихся ключей создают **все комбинации** (одна строка на каждую пару совпадений).

---

## split

- Если `f` — список факторов, используется их взаимодействие; уровни, содержащие `"."`, могут привести к неожиданным разбиениям, если не изменить `sep`.
- `drop = FALSE` (по умолчанию) сохраняет пустые уровни факторов как пустые элементы списка.
- Поддерживает синтаксис формул: `split(df, ~ Month)`.

---

## cbind / rbind

- `cbind` для фреймов данных вызывает `data.frame(...)`, а не `cbind.matrix`. Смешивание матриц и фреймов данных может дать неожиданные результаты.
- `rbind` для фреймов данных сопоставляет столбцы **по имени**, а не по позиции. Отсутствующие столбцы получают `NA`.
- `cbind(NULL)` возвращает `NULL` (не матрицу). Для согласованности `rbind(NULL)` также возвращает `NULL`.

---

## table

- По умолчанию **исключает NA** (`useNA = "no"`). Используйте `useNA = "ifany"` или `exclude = NULL`, чтобы подсчитывать NA.
- Непустое и не стандартное значение `exclude` подразумевает `useNA = "ifany"`.
- Результат всегда является **массивом** (даже одномерным), класса "table". Преобразуйте во фрейм данных через `as.data.frame(tbl)`.
- Два вида NA (NA как уровень фактора и фактический NA) обрабатываются по-разному в зависимости от `useNA`/`exclude`.

---

## duplicated / unique

- `duplicated` помечает **второе и последующие** вхождения как TRUE, не первое. Используйте `fromLast = TRUE` для обратного порядка.
- Для фреймов данных работает с целыми строками. Для списков выполняет рекурсивное сравнение.
- `unique` сохраняет **первое** вхождение каждого значения.

---

## data.frame (подводные камни)

- `stringsAsFactors = FALSE` — значение по умолчанию начиная с R 4.0.0 (раньше было TRUE).
- Атомарные векторы повторяются до длины самого длинного столбца, но только при точной кратности. Защитите их с помощью `I()`, чтобы предотвратить преобразование.
- Повторяющиеся имена столбцов допускаются только с `check.names = FALSE`, но многие операции незаметно устраняют повторы имён.
- Аргументы-матрицы разворачиваются в несколько столбцов, если не защищены `I()`.

---

## factor (подводные камни)

- `as.numeric(f)` возвращает **целочисленные коды**, а не исходные значения. Используйте `as.numeric(levels(f))[f]` или `as.numeric(as.character(f))`.
- Между факторами работают только `==` и `!=`; у факторов должны быть одинаковые наборы уровней. Упорядоченные факторы поддерживают `<`, `>`.
- `c()` для факторов объединяет наборы уровней (начиная с R 4.1.0), но более ранние версии преобразовывали их в целые числа.
- Уровни по умолчанию сортируются, но порядок сортировки **зависит от локали** на момент создания.

---

## aggregate

- Интерфейс формул (`aggregate(y ~ x, data, FUN)`) по умолчанию отбрасывает группы `NA`.
- Метод для фрейма данных требует, чтобы `by` был **списком** (не вектором).
- Возвращает столбцы, названные по группирующим переменным, а столбец результата сохраняет исходное имя.
- Если FUN возвращает несколько значений, столбец результата является **столбцом-матрицей** внутри фрейма данных.

---

## complete.cases

- Возвращает логический вектор: TRUE для строк, в которых **нет** NA во всех столбцах/аргументах.
- Работает с несколькими аргументами (например, `complete.cases(x, y)` проверяет оба).

---

## order

- Возвращает **вектор перестановки** индексов, а не отсортированные значения. Используйте `x[order(x)]` для сортировки.
- По умолчанию сортирует по возрастанию; используйте `-x` для числовой сортировки по убыванию или `decreasing = TRUE`.
- Сортировка символов зависит от локали. Используйте `method = "radix"` для быстрой сортировки, не зависящей от локали.
- `sort.int()` с `method = "radix"` значительно быстрее для больших целочисленных/символьных векторов.


## Файл: references/dates-and-system.md


# Даты и система — краткий справочник

> Неочевидное поведение, подводные камни и непростые значения по умолчанию функций R.
> Только то, чего Claude ещё не знает.

---

## Dates (класс Date)

- Объекты `Date` хранятся как **целое число дней с 1970-01-01**. Арифметика выполняется в днях.
- `Sys.Date()` возвращает текущую дату как объект Date.
- `seq.Date(from, to, by = "month")` — приращения "month" могут создавать интервалы разной длины. Прибавление 1 месяца к 31 января даёт 3 марта (не 28 февраля).
- `diff(dates)` возвращает объект `difftime` в днях.
- `format(date, "%Y")` — год, `"%m"` — месяц, `"%d"` — день, `"%A"` — название дня недели (зависит от локали).
- Годы до 1 года н. э. могут обрабатываться некорректно.
- `length(date_vector) <- n` при увеличении длины дополняет вектор значениями `NA`.

---

## DateTimeClasses (POSIXct / POSIXlt)

- `POSIXct`: секунды с 1970-01-01 UTC (компактный числовой вектор).
- `POSIXlt`: список с компонентами `$sec`, `$min`, `$hour`, `$mday`, `$mon` (0–11!), `$year` (с 1900 года!), `$wday` (0–6, воскресенье=0), `$yday` (0–365).
- Преобразование между POSIXct и Date: `as.Date(posixct_obj)` по умолчанию использует `tz = "UTC"` — может дать не ту дату, которую предполагали, если оригинал был в другом часовом поясе.
- `Sys.time()` возвращает POSIXct в текущем часовом поясе.
- `strptime` возвращает POSIXlt; для получения POSIXct используйте `as.POSIXct(strptime(...))`.
- Арифметика `difftime`: вычитание объектов POSIXct даёт difftime. Единицы выбираются автоматически ("secs", "mins", "hours", "days", "weeks").

---

## difftime

- `difftime(time1, time2, units = "auto")` — автоматически выбирает наименьшую разумную единицу измерения.
- Явные единицы: `"secs"`, `"mins"`, `"hours"`, `"days"`, `"weeks"`. Нет "months" или "years" (переменная длительность).
- `as.numeric(diff, units = "hours")` извлекает числовое значение в заданных единицах.
- `units(diff_obj) <- "hours"` меняет единицу непосредственно в объекте.

---

## system.time / proc.time

- `system.time(expr)` возвращает время `user`, `system` и `elapsed`.
- `gcFirst = TRUE` (по умолчанию): запускает сборку мусора перед замером для большей согласованности результатов.
- `proc.time()` возвращает накопленное время с запуска R — для интервалов вычисляйте разности.
- `elapsed` (реальное время по часам) может быть меньше `user` (многопоточный BLAS) или больше (ожидание ввода-вывода).

---

## Sys.sleep

- `Sys.sleep(seconds)` — допускает дробные секунды. Фактическое ожидание может оказаться дольше (планирование ОС).
- Во время ожидания процесс **уступает управление** ОС (не выполняет активное ожидание).

---

## options (ключевые параметры)

Избранные неочевидные параметры:

- `options(scipen = n)`: положительное значение смещает выбор к фиксированной записи, отрицательное — к научной. По умолчанию 0. Применяется к `print`/`format`/`cat`, но не к `sprintf`.
- `options(digits = n)`: число значащих цифр при печати (1–22, по умолчанию 7). Только рекомендация.
- `options(digits.secs = n)`: максимальное число десятичных знаков для секунд при форматировании времени (0–6, по умолчанию 0).
- `options(warn = n)`: -1 = игнорировать предупреждения, 0 = собирать (по умолчанию), 1 = выводить сразу, 2 = преобразовывать в ошибки.
- `options(error = recover)`: переход в отладчик при ошибке. `options(error = NULL)` сбрасывает к значению по умолчанию.
- `options(OutDec = ",")`: меняет десятичный разделитель в выводе (влияет на `format`, `print`, НЕ на `sprintf`).
- `options(stringsAsFactors = FALSE)`: глобальное значение по умолчанию для `data.frame` (неактуально с R 4.0.0, где оно уже FALSE).
- `options(expressions = 5000)`: максимальное число вложенных вычислений. Увеличивайте для глубокой рекурсии.
- `options(max.print = 99999)`: управляет усечением вывода `print`.
- `options(na.action = "na.omit")`: обработка NA по умолчанию в функциях моделирования.
- `options(contrasts = c("contr.treatment", "contr.poly"))`: контрасты по умолчанию для неупорядоченных/упорядоченных факторов.

---

## file.path / basename / dirname

- `file.path("a", "b", "c.txt")` → `"a/b/c.txt"` (разделитель, соответствующий платформе).
- `basename("/a/b/c.txt")` → `"c.txt"`. `dirname("/a/b/c.txt")` → `"/a/b"`.
- `file.path` НЕ нормализует пути (не разрешает `..`); для этого используйте `normalizePath()`.

---

## list.files

- `list.files(pattern = "*.csv")` — `pattern` является **регулярным выражением**, а не glob-шаблоном! Используйте `glob2rx("*.csv")` или `"\\.csv$"`.
- `full.names = FALSE` (по умолчанию) возвращает только базовые имена. Для полных путей используйте `full.names = TRUE`.
- `recursive = TRUE` — для поиска в подкаталогах.
- `all.files = TRUE` — для включения скрытых файлов (начинающихся с `.`).

---

## file.info

- Возвращает фрейм данных со столбцами `size`, `isdir`, `mode`, `mtime`, `ctime`, `atime`, `uid`, `gid`.
- `mtime`: время изменения (POSIXct). Полезно использовать `file.info(f)$mtime`.
- В некоторых файловых системах `ctime` — время изменения статуса, а не время создания.

---

## file_test

- `file_test("-f", path)`: TRUE, если обычный файл существует.
- `file_test("-d", path)`: TRUE, если каталог существует.
- `file_test("-nt", f1, f2)`: TRUE, если f1 новее f2.
- Надёжнее, чем `file.exists()`, для различения файлов и каталогов.


## Файл: references/io-and-text.md


# Ввод-вывод и обработка текста — краткий справочник

> Неочевидное поведение, подводные камни и непростые значения по умолчанию функций R.
> Только то, чего Claude ещё не знает.

---

## read.table (подводные камни)

- `sep = ""` (по умолчанию) означает **любой пробельный символ** (пробелы, табуляции, переводы строк), а не буквальную пустую строку.
- `comment.char = "#"` по умолчанию — строки с `#` усекаются. Для отключения используйте `comment.char = ""` (это также быстрее).
- Автоопределение `header`: устанавливается TRUE, если в первой строке **на одно поле меньше**, чем в последующих (предполагается, что недостающее поле — имена строк).
- `colClasses = "NULL"` полностью **пропускает** этот столбец — очень полезно для скорости.
- Значения по умолчанию `read.csv` отличаются от `read.table`: `header = TRUE`, `sep = ","`, `fill = TRUE`, `comment.char = ""`.
- Для больших файлов: указание `colClasses` и `nrows` резко уменьшает использование памяти. `read.table` медленна для широких фреймов данных (сотни столбцов); используйте `scan` или `data.table::fread` для матриц.
- `stringsAsFactors = FALSE` начиная с R 4.0.0 (раньше было TRUE).

---

## write.table (подводные камни)

- `row.names = TRUE` по умолчанию — создаёт безымянный первый столбец, мешающий повторному чтению. Используйте `row.names = FALSE` или `col.names = NA` для CSV, совместимого с Excel.
- `write.csv` фиксирует `sep = ","`, `dec = "."` и использует `qmethod = "double"` — переопределить их через `...` нельзя.
- `quote = TRUE` (по умолчанию) заключает символьные/факторные столбцы в кавычки. Числовые столбцы никогда не заключаются в кавычки.
- Матрицеподобные столбцы во фреймах данных незаметно разворачиваются в несколько столбцов.
- Медленна для фреймов данных с большим числом столбцов (сотни и больше); каждый столбец обрабатывается отдельно в соответствии с классом.

---

## read.fwf

- Читает файлы с полями фиксированной ширины. `widths` — вектор ширин полей.
- **Отрицательные значения ширины пропускают** соответствующее число символов (полезно для игнорирования полей).
- `buffersize` управляет числом строк, читаемых за один раз; увеличивайте для больших файлов.
- Внутри использует `read.table` после разделения полей.

---

## count.fields

- Подсчитывает поля в каждой строке файла — полезно для диагностики ошибок чтения.
- Аргументы `sep` и `quote` соответствуют аргументам `read.table`.

---

## grep / grepl / sub / gsub (подводные камни)

- Три режима регулярных выражений: расширенный POSIX (по умолчанию), `perl = TRUE`, `fixed = TRUE`. В крайних случаях ведут себя по-разному.
- **Явно именуйте аргументы** — безымянные аргументы после `x`/`pattern` позиционно сопоставляются с `ignore.case`, `perl` и т. д. Частый источник незаметных ошибок.
- `sub` заменяет только **первое** совпадение; `gsub` заменяет **все** совпадения.
- Обратные ссылки: `"\\1"` в замене (двойной обратный слеш в строках R). С `perl = TRUE`: `"\\U\\1"` для преобразования в верхний регистр.
- `grep(value = TRUE)` возвращает совпавшие **элементы**; `grep(value = FALSE)` (по умолчанию) возвращает **индексы**.
- `grepl` возвращает логический вектор — предпочтительна для фильтрации.
- `regexpr` возвращает позицию первого совпадения + длину (как атрибуты); `gregexpr` возвращает все совпадения списком.
- `regexec` возвращает позиции совпадения + захватывающих групп; `gregexec` делает это для всех совпадений.
- Классы символов наподобие `[:alpha:]` в режиме POSIX должны находиться внутри `[[:alpha:]]` (двойные скобки).

---

## strsplit

- Возвращает **список** (один элемент на каждую входную строку), даже для единственной строки.
- `split = ""` или `split = character(0)` разбивает на отдельные символы.
- Совпадение в начале строки: первый элемент результата — `""`. Совпадение в конце: завершающего `""` нет.
- `fixed = TRUE` работает быстрее и исключает интерпретацию как регулярного выражения.
- Частая ошибка: безымянные аргументы незаметно сопоставляются с `fixed`, `perl` и т. д.

---

## substr / substring

- `substr(x, start, stop)`: извлекает/заменяет подстроку. Индексация с 1, обе границы включаются.
- `substring(x, first, last)`: то же, но `last` по умолчанию равно `1000000L` (фактически «до конца»). Векторизована по `first`/`last`.
- Форма присваивания: `substr(x, 1, 3) <- "abc"` выполняет замену непосредственно в объекте (замена должна иметь ту же длину).

---

## trimws

- `which = "both"` (по умолчанию), `"left"` или `"right"`.
- `whitespace = "[ \\t\\r\\n]"` — настраиваемое регулярное выражение, определяющее, что считать пробельным символом.

---

## nchar

- `type = "bytes"` считает байты; `type = "chars"` (по умолчанию) считает символы; `type = "width"` считает ширину отображения.
- `nchar(NA)` возвращает `NA` (не 2). `nchar(factor)` работает с метками уровней.
- `keepNA = TRUE` (по умолчанию с R 3.3.0); задайте `FALSE`, чтобы считать `"NA"` двумя символами.

---

## format / formatC

- `format(x, digits, nsmall)`: `nsmall` задаёт минимальное число знаков после десятичного разделителя. `big.mark = ","` добавляет разделитель тысяч.
- `formatC(x, format = "f", digits = 2)`: форматирование в стиле C. `format = "e"` — научная запись, `"g"` — общий формат.
- `format` возвращает символьный вектор; по умолчанию всегда выравнивает вправо (`justify = "right"`).

---

## type.convert

- Преобразует символьные векторы в подходящие типы (логический, целочисленный, double, комплексный, символьный).
- `as.is = TRUE` (рекомендуется): сохраняет символьные данные символьными, а не факторами.
- Применяется по столбцам фреймов данных. `tryLogical = TRUE` (R 4.3+) преобразует столбцы "TRUE"/"FALSE".

---

## Rscript

- `commandArgs(trailingOnly = TRUE)` получает аргументы скрипта (без флагов R/Rscript).
- Строка `#!` в Unix: `/usr/bin/env Rscript` или полный путь.
- `--vanilla` или `--no-init-file` — для пропуска загрузки `.Rprofile`.
- Код выхода: `quit(status = 1)` для завершения с ошибкой.

---

## capture.output

- Перехватывает вывод `cat`, `print` или любого выражения, записывающего в stdout.
- `file = NULL` (по умолчанию) возвращает символьный вектор. `file = "out.txt"` записывает непосредственно в файл.
- `type = "message"` вместо этого перехватывает stderr.

---

## URLencode / URLdecode

- `URLencode(url, reserved = FALSE)` по умолчанию НЕ кодирует зарезервированные символы (`/`, `?`, `&` и т. д.).
- Задайте `reserved = TRUE` для кодирования **компонента** URL (значения параметра запроса).

---

## glob2rx

- Преобразует glob-шаблоны оболочки в регулярные выражения: `glob2rx("*.csv")` → `"^.*\\.csv$"`.
- Полезно вместе с `list.files(pattern = glob2rx("data_*.RDS"))`.


## Файл: references/modeling.md


# Моделирование — краткий справочник

> Неочевидное поведение, подводные камни и непростые значения по умолчанию функций R.
> Только то, чего Claude ещё не знает.

---

## formula

Подводные камни символьного задания модели.

- `I()` необходима для буквального использования арифметических операторов: `y ~ x + I(x^2)`. Без `I()` оператор `^` означает комбинации взаимодействий.
- `*` = главные эффекты + взаимодействие: `a*b` разворачивается в `a + b + a:b`.
- `(a+b+c)^2` = все главные эффекты + все двухфакторные взаимодействия (не возведение в квадрат).
- `-` удаляет члены: `(a+b+c)^2 - a:b` исключает только взаимодействие `a:b`.
- `/` означает вложенность: `a/b` = `a + b %in% a` = `a + a:b`.
- `.` в формуле означает «все остальные столбцы данных» (в контексте `terms.formula`) или «предыдущее содержимое» (в `update.formula`).
- Объекты формул содержат **окружение**, используемое для поиска переменных; `as.formula("y ~ x")` использует `parent.frame()`.

---

## terms / model.matrix

- `model.matrix` создаёт матрицу плана, включая кодирование фиктивными переменными. Контрасты по умолчанию: `contr.treatment` для неупорядоченных факторов, `contr.poly` для упорядоченных.
- Атрибуты объекта `terms`: `order` (порядок взаимодействия для каждого члена), `intercept`, матрица `factors`.
- Имена столбцов из `model.matrix` могут быть неожиданными: например, конкатенация `factorLevelName`.

---

## glm

- По умолчанию `family = gaussian(link = "identity")` — `glm()` без `family` незаметно строит OLS-модель (то же, что `lm`, но медленнее и с выводом на основе девианса).
- Распространённые семейства: `binomial(link = "logit")`, `poisson(link = "log")`, `Gamma(link = "inverse")`, `inverse.gaussian()`.
- `binomial` принимает отклик в виде: вектора 0/1, логических значений, фактора (второй уровень = успех) или матрицы с 2 столбцами `cbind(success, failure)`.
- `weights` в `glm` означает **априорные веса** (не частотные веса) — для частотных весов используйте приём с cbind или offset.
- `predict.glm(type = "response")` — для предсказанных вероятностей; по умолчанию `type = "link"` возвращает логарифм шансов (для логистической модели) или логарифм интенсивности (для модели Пуассона).
- `anova(glm_obj, test = "Chisq")` — для тестов на основе девианса; `"F"` недопустим для негауссовых семейств.
- Квазисемейства (`quasibinomial`, `quasipoisson`) допускают сверхдисперсию — AIC не вычисляется.
- Сходимость: `control = glm.control(maxit = 100)`, если стандартных 25 итераций недостаточно.

---

## aov

- `aov` — обёртка над `lm`, сохраняющая дополнительную информацию для сбалансированного ANOVA. Для несбалансированных планов вычисляются SS типа I (последовательные) — порядок членов важен.
- Для SS типа III используйте `car::Anova()` или установите контрасты `contr.sum`/`contr.helmert`.
- Страты ошибки для повторных измерений: `aov(y ~ A*B + Error(Subject/B))`.
- `summary.aov` выдаёт таблицу ANOVA; `summary.lm(aov_obj)` — сводку в стиле регрессии.

---

## nls

- Требует **хороших начальных значений** в `start = list(...)`, иначе сходимость не достигается.
- Самозапускающиеся модели (`SSlogis`, `SSasymp` и т. д.) автоматически вычисляют начальные значения.
- Алгоритм `"port"` допускает ограничения параметров (`lower`/`upper`).
- Если данные описываются слишком точно (нет остаточного шума), проверка сходимости не проходит — используйте `control = list(scaleOffset = 1)` или добавьте небольшой случайный шум к данным.
- Аргумент `weights` — для взвешенного NLS; `na.action` — для обработки пропущенных значений.

---

## step / add1

- `step` выполняет **пошаговый** выбор модели по AIC (по умолчанию). Используйте `k = log(n)` для BIC.
- Направление: `direction = "both"` (по умолчанию), `"forward"` или `"backward"`.
- `add1`/`drop1` оценивают добавления/удаления одного члена; `step` вызывает их итеративно.
- Аргумент `scope` задаёт верхнюю/нижнюю границы модели для поиска.
- `step` изменяет объект модели непосредственно — может работать медленно для больших моделей с множеством потенциальных членов.

---

## predict.lm / predict.glm

- `predict.lm` с `interval = "confidence"` даёт доверительный интервал для **среднего** отклика; `interval = "prediction"` даёт предиктивный интервал для **нового наблюдения** (более широкий).
- `newdata` должен содержать столбцы, соответствующие переменным исходной формулы; факторы должны иметь те же уровни.
- `predict.glm` с `type = "response"` выдаёт прогнозы в шкале отклика (например, вероятности для логистической модели); `type = "link"` (по умолчанию) — в шкале функции связи.
- `se.fit = TRUE` возвращает стандартные ошибки; для `predict.glm` они задаются в шкале **функции связи** независимо от `type`.
- `predict.lm` с `type = "terms"` возвращает вклад каждого члена.

---

## loess

- `span` управляет гладкостью (по умолчанию 0.75). Span < 1 использует такую долю точек; span > 1 использует все точки с скорректированным расстоянием.
- Максимум **4 предиктора**. Использование памяти приблизительно **квадратично** по n (1000 точек ~ 10MB).
- `degree = 0` (локальная константа) допускается, но плохо протестировано — используйте осторожно.
- Не идентична `loess` из S; обусловливание не реализовано.
- `normalize = TRUE` (по умолчанию) стандартизирует предикторы к общей шкале; для пространственных координат задавайте `FALSE`.

---

## lowess в сравнении с loess

- `lowess` — более старая функция; возвращает `list(x, y)` — не позволяет прогнозировать в новых точках.
- `loess` — более новый интерфейс формул с методом `predict`.
- Параметр `lowess` — `f` (span, по умолчанию 2/3); параметр `loess` — `span` (по умолчанию 0.75).
- Для `lowess` значение `iter` по умолчанию равно 3 (итерации повышения робастности); для `loess` по умолчанию `family = "gaussian"` (без робастности).

---

## smooth.spline

- По умолчанию параметр сглаживания выбирается через **GCV** (обобщённую перекрёстную проверку).
- `cv = TRUE` вместо этого использует обычную перекрёстную проверку с исключением одного наблюдения — не используйте при повторяющихся значениях x.
- `spar` и `lambda` управляют гладкостью; `df` может задавать эквивалентное число степеней свободы.
- Возвращает объект с методами `predict`, `print`, `plot`. Компонент `fit` содержит узлы и коэффициенты.

---

## optim

- По умолчанию **минимизирует**. Для максимизации задайте `control = list(fnscale = -1)`.
- Метод по умолчанию — Нелдера — Мида (без градиентов, устойчивый, но медленный). Плохо подходит для одномерных задач — используйте `"Brent"` или `optimize()`.
- `"L-BFGS-B"` — единственный метод, поддерживающий прямоугольные ограничения (`lower`/`upper`). Задание границ автоматически выбирает этот метод с предупреждением.
- `"SANN"` (имитация отжига): код сходимости **всегда 0** — он никогда не «завершается неудачей». `maxit` = общее число вычислений функции (по умолчанию 10000), другого критерия остановки нет.
- `parscale`: масштабируйте параметры так, чтобы изменение каждого на единицу давало сопоставимое изменение целевой функции. Критически важно для задач с разными масштабами.
- `hessian = TRUE`: возвращает численный гессиан задачи **без ограничений**, даже если прямоугольные ограничения активны.
- `fn` может возвращать `NA`/`Inf` (кроме `"L-BFGS-B"`, который всегда требует конечных значений). Начальное значение должно быть конечным.

---

## optimize / uniroot

- `optimize`: одномерная минимизация на ограниченном интервале. Возвращает `minimum` и `objective`.
- `uniroot`: находит корень `f` в `[lower, upper]`. **Требует**, чтобы `f(lower)` и `f(upper)` имели противоположные знаки.
- `uniroot` с `extendInt = "yes"` может автоматически расширять интервал для поиска смены знака — но может находить ложные корни у функций, которые на самом деле не пересекают ноль.
- `nlm`: минимизатор ньютоновского типа. Градиент/гессиан задаются как **атрибуты** возвращаемого `fn` значения (необычный интерфейс).

---

## TukeyHSD

- Требует обученный объект `aov` (не `lm`).
- По умолчанию `conf.level = 0.95`. Возвращает скорректированные p-значения и доверительные интервалы для всех попарных сравнений.
- Имеет смысл только для **сбалансированных** или почти сбалансированных планов; для сильно несбалансированных данных может быть излишне либеральным.

---

## anova (для lm)

- `anova(model)`: последовательные SS (тип I) — **порядок членов важен**.
- `anova(model1, model2)`: F-тест для сравнения вложенных моделей.
- Для SS типа II или III используйте `car::Anova()`.


## Файл: references/statistics.md


# Статистика — краткий справочник

> Неочевидное поведение, подводные камни и непростые значения по умолчанию функций R.
> Только то, чего Claude ещё не знает.

---

## chisq.test

- `correct = TRUE` (по умолчанию) применяет поправку Йейтса на непрерывность **только для таблиц 2x2**.
- `simulate.p.value = TRUE`: метод Монте-Карло с `B = 2000` повторениями (минимальное p ~ 0.0005). Моделирование предполагает **фиксированные маргинальные суммы** (выборка в стиле Фишера, а не предположение хи-квадрат).
- Для проверки согласия: передавайте вектор, не матрицу. Сумма `p` должна равняться 1 (или задайте `rescale.p = TRUE`).
- Возвращаемый объект включает `$expected`, `$residuals` (Пирсона) и `$stdres` (стандартизированные).

---

## wilcox.test

- `exact = TRUE` по умолчанию для малых выборок без связанных рангов. При связанных рангах используется нормальная аппроксимация.
- `correct = TRUE` применяет поправку на непрерывность к нормальной аппроксимации.
- `conf.int = TRUE` вычисляет оценку Ходжеса — Лемана и доверительный интервал (не только p-значение).
- Парный тест: `paired = TRUE` использует критерий знаковых рангов (Уилкоксона), а не суммы рангов (Манна — Уитни).

---

## fisher.test

- Для таблиц больше 2x2 использует моделирование (`simulate.p.value = TRUE`) или сетевой алгоритм.
- `workspace` управляет памятью для сетевого алгоритма; увеличивайте, если возникают ошибки на больших таблицах.
- Аргумент `or` проверяет конкретное отношение шансов (по умолчанию 1) — только для таблиц 2x2.

---

## ks.test

- Двухвыборочный тест или одновыборочный тест против эталонного распределения.
- **Не** справляется хорошо с совпадающими значениями — выдаёт предупреждение и использует асимптотическую аппроксимацию.
- Для составных гипотез (параметры оцениваются по данным) p-значения **консервативны** (слишком велики). Используйте `dgof` или `ks.test` с `exact = NULL` для дискретных распределений.

---

## p.adjust

- Методы: `"holm"` (по умолчанию), `"BH"` (FDR Бенджамини — Хохберга), `"bonferroni"`, `"BY"`, `"hochberg"`, `"hommel"`, `"fdr"` (псевдоним BH), `"none"`.
- Аргумент `n`: общее число гипотез (может быть больше `length(p)`, если некоторые p-значения исключены).
- Обрабатывает `NA`: скорректированные p-значения равны `NA` там, где входное значение — `NA`.

---

## pairwise.t.test / pairwise.wilcox.test

- `p.adjust.method` по умолчанию равен `"holm"`. Измените на `"BH"` для контроля FDR.
- `pool.sd = TRUE` (по умолчанию для t-теста): использует объединённое стандартное отклонение по всем группам (предполагает равные дисперсии).
- Возвращает матрицу p-значений, а не статистик теста.

---

## shapiro.test

- Размер выборки должен быть от 3 до 5000.
- Проверяет нормальность; низкое p-значение = свидетельство против нормальности.

---

## kmeans

- Рекомендуется `nstart > 1` (например, `nstart = 25`): запускает алгоритм из нескольких случайных начальных состояний и возвращает лучшее.
- По умолчанию `iter.max = 10` — может быть слишком мало для сходимости. Увеличивайте для больших/сложных данных.
- Алгоритм по умолчанию — "Hartigan-Wong" (обычно лучший). Очень близкие точки могут вызвать несходимость (предупреждение с `ifault = 4`).
- Нумерация кластеров произвольна; порядок может различаться между платформами.
- Всегда возвращает k кластеров, когда задано k (кроме Lloyd-Forgy, который может вернуть меньше).

---

## hclust

- `method = "ward.D2"` корректно реализует критерий Уорда (с использованием квадратов расстояний). Более старый `"ward.D"` не возводил расстояния в квадрат (сохранён для обратной совместимости).
- Вход должен быть объектом `dist`. Используйте `as.dist()` для преобразования симметричной матрицы.
- `hang = -1` в `plot()` выравнивает все подписи внизу.

---

## dist

- `method = "euclidean"` (по умолчанию). Другие варианты: `"manhattan"`, `"maximum"`, `"canberra"`, `"binary"`, `"minkowski"`.
- Возвращает объект `dist` (только нижний треугольник). Для полной матрицы используйте `as.matrix()`.
- `"canberra"`: члены с нулевыми числителем и знаменателем **исключаются** из суммы (не трактуются как 0/0).
- Значения `Inf`: евклидово расстояние с участием `Inf` равно `Inf`. Несколько `Inf` в одном наблюдении дают `NaN` для некоторых методов.

---

## prcomp в сравнении с princomp

- `prcomp` использует **SVD** (численно более совершенный подход); `princomp` использует `eigen` для ковариации (менее устойчивый, масштабирование N-1 в сравнении с N).
- `scale. = TRUE` в `prcomp` стандартизирует переменные; важно, если масштабы переменных сильно различаются.
- Стандартные отклонения `princomp` отличаются от `prcomp` множителем `sqrt((n-1)/n)`.
- Обе возвращают `$rotation` (нагрузки) и `$x` (значения компонент); знаки компонент могут различаться между запусками.

---

## density

- Ширина окна по умолчанию: `bw = "nrd0"` (эмпирическое правило Сильвермана). Для мультимодальных данных рассмотрите `"SJ"` или `"bcv"`.
- `adjust`: множитель ширины окна. `adjust = 0.5` уменьшает ширину вдвое (меньше сглаживания).
- Ядро по умолчанию: `"gaussian"`. Диапазон плотности выходит за диапазон данных (управляется `cut`, по умолчанию 3 ширины окна).
- `n = 512`: число точек вычисления. Увеличивайте для более гладкого графика.
- `from`/`to`: явно ограничивают диапазон вычисления.

---

## quantile

- **Девять** вариантов `type` (1–9). По умолчанию `type = 7` (стандарт R, линейная интерполяция). Тип 1 = обратная функция эмпирической CDF (по умолчанию в SAS). Типы 4–9 непрерывны; 1–3 разрывны.
- `na.rm = FALSE` по умолчанию — возвращает NA при наличии любых NA.
- `names = TRUE` по умолчанию, добавляет "0%", "25%" и т. д. в качестве имён.

---

## Распределения (общие подводные камни)

Все функции распределений следуют шаблону `d/p/q/r`. Общие неочевидные моменты:

- **Аргумент `n` в функциях `r*()`**: если `length(n) > 1`, в качестве количества используется `length(n)`, а не само `n`. Поэтому `rnorm(c(1,2,3))` генерирует 3 значения, а не 1+2+3.
- `log = TRUE` / `log.p = TRUE`: вычисления в логарифмической шкале для численной устойчивости в хвостах.
- `lower.tail = FALSE` непосредственно даёт функцию выживания P(X > x) (в хвостах точнее, чем 1 - pnorm()).
- **Гамма-распределение**: параметризуется через `shape` и `rate` (= 1/scale). По умолчанию `rate = 1`. Одновременное указание `rate` и `scale` — ошибка.
- **Бета-распределение**: `shape1` (альфа), `shape2` (бета) — параметризации через `mean`/`sd` нет.
- **Пуассон `dpois`**: `x` может быть нецелым (для нецелых значений возвращает 0 с предупреждением, если `log = FALSE`).
- **Вейбулл**: `shape` и `scale` (без `rate`). Параметризация R: `f(x) = (shape/scale)(x/scale)^(shape-1) exp(-(x/scale)^shape)`.
- **Логнормальное распределение**: `meanlog` и `sdlog` — среднее/стандартное отклонение **логарифма**, а не самого распределения.

---

## cor.test

- Метод по умолчанию: `"pearson"`. Также `"kendall"` и `"spearman"`.
- Возвращает `$estimate`, `$p.value`, `$conf.int` (доверительный интервал только для Пирсона).
- Интерфейс формул: `cor.test(~ x + y, data = df)` — обратите внимание на `~` без левой части.

---

## ecdf

- Возвращает **функцию** (ступенчатую). Вызывайте её для новых значений: `Fn <- ecdf(x); Fn(3.5)`.
- `plot(ecdf(x))` строит график эмпирической CDF.
- Возвращаемая функция непрерывна справа и имеет левые пределы (cadlag).

---

## weighted.mean

- Обрабатывает `NA` в весах: наблюдение исключается, если вес равен `NA`.
- Сумма весов не обязана равняться 1; они нормализуются внутри функции.


## Файл: references/visualization.md


# Визуализация — краткий справочник

> Неочевидное поведение, подводные камни и непростые значения по умолчанию функций R.
> Только то, чего Claude ещё не знает.

---

## par (подводные камни)

- Настройки `par()` относятся к конкретному устройству. Открытие нового устройства всё сбрасывает.
- Установка `mfrow`/`mfcol` сбрасывает `cex` в 1 и `mex` в 1. При размещении 2x2 базовое `cex` умножается на 0.83; при 3 и более строках/столбцах — на 0.66.
- `mai` (дюймы), `mar` (строки), `pin`, `plt`, `pty` взаимодействуют друг с другом. Восстановление всех сохранённых параметров после изменения размера устройства может дать несогласованные результаты — побеждает последний по алфавиту.
- Установка `bg` через `par()` также задаёт `new = FALSE`. Установка `fg` через `par()` также задаёт `col`.
- `xpd = NA` обрезает по области устройства (позволяет рисовать на внешних полях); `xpd = TRUE` обрезает по области рисунка; `xpd = FALSE` (по умолчанию) обрезает по области графика.
- `mgp = c(3, 1, 0)`: управляет строкой заголовка (`mgp[1]`), строкой подписей (`mgp[2]`), линией оси (`mgp[3]`). Всё в единицах `mex`.
- `las`: 0 = параллельно оси, 1 = горизонтально, 2 = перпендикулярно, 3 = вертикально. **Не** реагирует на `srt`.
- `tck = 1` рисует линии сетки через весь график. `tcl = -0.5` (по умолчанию) даёт засечки наружу.
- `usr` при логарифмической шкале: содержит **log10** границ координат, а не исходные значения.
- Параметры только для чтения: `cin`, `cra`, `csi`, `cxy`, `din`, `page`.

---

## layout

- `layout(mat)`, где `mat` — матрица целых чисел, задающая расположение рисунков.
- `widths`/`heights` принимают `lcm()` для абсолютных размеров, смешанных с относительными.
- Гибче, чем `mfrow`/`mfcol`, но после установки прочитать конфигурацию нельзя (в отличие от `par("mfrow")`).
- `layout.show(n)` визуализирует расположение для отладки.

---

## axis / mtext

- `axis(side, at, labels)`: `side` 1=снизу, 2=слева, 3=сверху, 4=справа.
- Интервал между подписями осей по умолчанию управляется `par("mgp")`. Без контроля подписи могут перекрываться.
- `mtext`: аргумент `line` размещает текст по строкам поля (0 = рядом с графиком, положительное значение = наружу). `adj` управляет горизонтальным положением (0–1).
- `mtext` с `outer = TRUE` пишет на **внешнем** поле (задаётся через `par(oma = ...)`).

---

## curve

- Первый аргумент может быть **выражением** от `x` или функцией: `curve(sin, 0, 2*pi)` или `curve(x^2 + 1, 0, 10)`.
- `add = TRUE` — для наложения на существующий график. По умолчанию `n = 101` точка вычисления.
- По умолчанию `xname = "x"`; измените, если в выражении используется другое имя переменной.

---

## pairs

- Функция `panel` получает `(x, y, ...)` для каждой пары. `lower.panel`, `upper.panel`, `diag.panel` — для разных областей.
- `gap` управляет промежутками между панелями (по умолчанию 1).
- Интерфейс формул: `pairs(~ var1 + var2 + var3, data = df)`.

---

## coplot

- Условные графики: `coplot(y ~ x | a)` или `coplot(y ~ x | a * b)` для двух обусловливающих переменных.
- Функцию `panel` можно настраивать; `rows`/`columns` управляют расположением.
- Панель по умолчанию рисует точки; используйте `panel = panel.smooth` для наложения loess.

---

## matplot / matlines / matpoints

- Строит графики столбцов одной матрицы относительно столбцов другой. Циклически повторяет `col`, `lty`, `pch` для столбцов.
- По умолчанию `type = "l"` (в отличие от `plot`, где по умолчанию `"p"`).
- Полезно для одновременного отображения нескольких временных рядов или подобранных кривых.

---

## contour / filled.contour / image

- `contour(x, y, z)`: `z` должна быть матрицей с `dim = c(length(x), length(y))`.
- `filled.contour` имеет нестандартное расположение — создаёт собственную область графика для цветовой шкалы. **Нельзя использовать с ней `par(mfrow)`**. Для добавления элементов требуется аргумент `plot.axes`.
- `image`: отображает значения z как цветные прямоугольники. Цветовая схема по умолчанию может вводить в заблуждение; задавайте `col` явно.
- Для `image` переменные `x` и `y` задают **границы ячеек** или **середины** в зависимости от контекста.

---

## persp

- `persp(x, y, z, theta, phi)`: `theta` = азимутальный угол, `phi` = полярный угол.
- Возвращает **матрицу преобразования** (невидимо) для проекции 3D в 2D — используйте `trans3d()` для добавления точек/линий на перспективный график.
- `shade` и `col` управляют затенением поверхности. `border = NA` убирает линии сетки.

---

## segments / arrows / rect / polygon

- Все принимают векторизованные координаты; при необходимости циклически повторяют значения.
- `arrows`: `code = 1` (наконечник в начале), `code = 2` (наконечник в конце, по умолчанию), `code = 3` (с обоих концов).
- `polygon`: последняя точка автоматически соединяется с первой. Заливка через `col`; `border` управляет контуром.
- `rect(xleft, ybottom, xright, ytop)` — обратите внимание: порядок аргументов отличается от других систем.

---

## dev / dev.off / dev.copy

- `dev.new()` открывает новое устройство. `dev.off()` закрывает текущее устройство (и сбрасывает вывод для файловых устройств, таких как `pdf`).
- `dev.off()` для **последнего** открытого устройства возвращает к нулевому устройству.
- `dev.copy(pdf, file = "plot.pdf")`, затем `dev.off()` — для сохранения текущего графика.
- `dev.list()` возвращает все открытые устройства; `dev.cur()` — активное.

---

## pdf

- Для завершения записи файла необходимо вызвать `dev.off()`. Без этого файл может быть пустым/повреждённым.
- `onefile = TRUE` (по умолчанию): несколько страниц в одном PDF. `onefile = FALSE`: по одному файлу на страницу (использует `%d` в имени файла для нумерации).
- Рекомендуется `useDingbats = FALSE`, чтобы избежать проблем с некоторыми средствами просмотра PDF и символами pch.
- Размер по умолчанию: 7x7 дюймов. `family` управляет семейством шрифта.

---

## png / растровые устройства

- `res` управляет DPI (по умолчанию 72). Для публикации: `res = 300` с подходящими `width`/`height` в пикселях или дюймах (с `units = "in"`).
- `type = "cairo"` (в системах с cairo) даёт лучшее сглаживание, чем стандартный вариант.
- `bg = "transparent"` — для прозрачного фона (PNG поддерживает альфа-канал).

---

## colors / rgb / hcl / col2rgb

- `colors()` возвращает все 657 именованных цветов. `col2rgb("color")` возвращает матрицу RGB.
- `rgb(r, g, b, alpha, maxColorValue = 255)` — обратите внимание: по умолчанию `maxColorValue` равен 1, а не 255.
- `hcl(h, c, l)`: перцептивно равномерное цветовое пространство. Предпочтительно для цветовых шкал.
- `adjustcolor(col, alpha.f = 0.5)`: простой способ добавить прозрачность.

---

## colorRamp / colorRampPalette

- `colorRamp` возвращает **функцию**, отображающую [0,1] в матрицу RGB.
- `colorRampPalette` возвращает **функцию**, принимающую `n` и возвращающую `n` интерполированных цветов.
- `space = "Lab"` даёт более равномерную по восприятию интерполяцию, чем `"rgb"`.

---

## palette / recordPlot

- `palette()` возвращает текущую палитру (по умолчанию 8 цветов). `palette("Set1")` устанавливает встроенную палитру.
- Целочисленные значения цвета на графиках индексируют палитру (с циклическим повторением). Индекс 0 = цвет фона.
- `recordPlot()` / `replayPlot()`: сохраняют и восстанавливают полный график — зависят от устройства и ненадёжны между сессиями.


## Файл: assets/analysis_template.R


# ============================================================
# Analysis Template — Base R
# Copy this file, rename it, and fill in your details.
# ============================================================
# Author  : 
# Date    : 
# Data    : 
# Purpose : 
# ============================================================


# ── 0. Setup ─────────────────────────────────────────────────
# Clear environment (optional — comment out if loading into existing session)
rm(list = ls())

# Set working directory if needed
# setwd("/path/to/your/project")

# Reproducibility
set.seed(42)

# Libraries — uncomment what you need
# library(haven)        # read .dta / .sav / .sas
# library(readxl)       # read Excel files
# library(openxlsx)     # write Excel files
# library(foreign)      # older Stata / SPSS formats
# library(survey)       # survey-weighted analysis
# library(lmtest)       # Breusch-Pagan, Durbin-Watson etc.
# library(sandwich)     # robust standard errors
# library(car)          # Type II/III ANOVA, VIF


# ── 1. Load Data ─────────────────────────────────────────────
df <- read.csv("your_data.csv", stringsAsFactors = FALSE)
# df <- readRDS("your_data.rds")
# df <- haven::read_dta("your_data.dta")

# First look — always run these
dim(df)
str(df)
head(df, 10)
summary(df)


# ── 2. Data Quality Check ────────────────────────────────────
# Missing values
na_report <- data.frame(
  column   = names(df),
  n_miss   = colSums(is.na(df)),
  pct_miss = round(colMeans(is.na(df)) * 100, 1),
  row.names = NULL
)
print(na_report[na_report$n_miss > 0, ])

# Duplicates
n_dup <- sum(duplicated(df))
cat(sprintf("Duplicate rows: %d\n", n_dup))

# Unique values for categorical columns
cat_cols <- names(df)[sapply(df, function(x) is.character(x) | is.factor(x))]
for (col in cat_cols) {
  cat(sprintf("\n%s (%d unique):\n", col, length(unique(df[[col]]))))
  print(table(df[[col]], useNA = "ifany"))
}


# ── 3. Clean & Transform ─────────────────────────────────────
# Rename columns (example)
# names(df)[names(df) == "old_name"] <- "new_name"

# Convert types
# df$group <- as.factor(df$group)
# df$date  <- as.Date(df$date, format = "%Y-%m-%d")

# Recode values (example)
# df$gender <- ifelse(df$gender == 1, "Male", "Female")

# Create new variables (example)
# df$log_income <- log(df$income + 1)
# df$age_group  <- cut(df$age,
#                      breaks = c(0, 25, 45, 65, Inf),
#                      labels = c("18-25", "26-45", "46-65", "65+"))

# Filter rows (example)
# df <- df[df$year >= 2010, ]
# df <- df[complete.cases(df[, c("outcome", "predictor")]), ]

# Drop unused factor levels
# df <- droplevels(df)


# ── 4. Descriptive Statistics ────────────────────────────────
# Numeric summary
num_cols <- names(df)[sapply(df, is.numeric)]
round(sapply(df[num_cols], function(x) c(
  n      = sum(!is.na(x)),
  mean   = mean(x, na.rm = TRUE),
  sd     = sd(x, na.rm = TRUE),
  median = median(x, na.rm = TRUE),
  min    = min(x, na.rm = TRUE),
  max    = max(x, na.rm = TRUE)
)), 3)

# Cross-tabulation
# table(df$group, df$category, useNA = "ifany")
# prop.table(table(df$group, df$category), margin = 1)  # row proportions


# ── 5. Visualization (EDA) ───────────────────────────────────
par(mfrow = c(2, 2))

# Histogram of main outcome
hist(df$outcome_var,
     main   = "Distribution of Outcome",
     xlab   = "Outcome",
     col    = "steelblue",
     border = "white",
     breaks = 30)

# Boxplot by group
boxplot(outcome_var ~ group_var,
        data = df,
        main = "Outcome by Group",
        col  = "lightyellow",
        las  = 2)

# Scatter plot
plot(df$predictor, df$outcome_var,
     main = "Predictor vs Outcome",
     xlab = "Predictor",
     ylab = "Outcome",
     pch  = 19,
     col  = adjustcolor("steelblue", alpha.f = 0.5),
     cex  = 0.8)
abline(lm(outcome_var ~ predictor, data = df),
       col = "red", lwd = 2)

# Correlation matrix (numeric columns only)
cor_mat <- cor(df[num_cols], use = "complete.obs")
image(cor_mat,
      main = "Correlation Matrix",
      col  = hcl.colors(20, "RdBu", rev = TRUE))

par(mfrow = c(1, 1))


# ── 6. Analysis ───────────────────────────────────────────────

# ·· 6a. Comparison of means ··
t.test(outcome_var ~ group_var, data = df)

# ·· 6b. Linear regression ··
fit <- lm(outcome_var ~ predictor1 + predictor2 + group_var,
          data = df)
summary(fit)
confint(fit)

# Check VIF for multicollinearity (requires car)
# car::vif(fit)

# Robust standard errors (requires lmtest + sandwich)
# lmtest::coeftest(fit, vcov = sandwich::vcovHC(fit, type = "HC3"))

# ·· 6c. ANOVA ··
# fit_aov <- aov(outcome_var ~ group_var, data = df)
# summary(fit_aov)
# TukeyHSD(fit_aov)

# ·· 6d. Logistic regression (binary outcome) ··
# fit_logit <- glm(binary_outcome ~ x1 + x2,
#                  data   = df,
#                  family = binomial(link = "logit"))
# summary(fit_logit)
# exp(coef(fit_logit))         # odds ratios
# exp(confint(fit_logit))      # OR confidence intervals


# ── 7. Model Diagnostics ─────────────────────────────────────
par(mfrow = c(2, 2))
plot(fit)
par(mfrow = c(1, 1))

# Residual normality
shapiro.test(residuals(fit))

# Homoscedasticity (requires lmtest)
# lmtest::bptest(fit)


# ── 8. Save Output ────────────────────────────────────────────
# Cleaned data
# write.csv(df, "data_clean.csv", row.names = FALSE)
# saveRDS(df, "data_clean.rds")

# Model results to text file
# sink("results.txt")
# cat("=== Linear Model ===\n")
# print(summary(fit))
# cat("\n=== Confidence Intervals ===\n")
# print(confint(fit))
# sink()

# Plots to file
# png("figure1_distributions.png", width = 1200, height = 900, res = 150)
# par(mfrow = c(2, 2))
# # ... your plots ...
# par(mfrow = c(1, 1))
# dev.off()

# ============================================================
# END OF TEMPLATE
# ============================================================


## Файл: scripts/check_data.R


# check_data.R — Quick data quality report for any R data frame
# Usage: source("check_data.R") then call check_data(df)
# Or:    source("check_data.R"); check_data(read.csv("yourfile.csv"))

check_data <- function(df, top_n_levels = 8) {
  
  if (!is.data.frame(df)) stop("Input must be a data frame.")
  
  n_row <- nrow(df)
  n_col <- ncol(df)
  
  cat("══════════════════════════════════════════\n")
  cat("  DATA QUALITY REPORT\n")
  cat("══════════════════════════════════════════\n")
  cat(sprintf("  Rows: %d    Columns: %d\n", n_row, n_col))
  cat("══════════════════════════════════════════\n\n")
  
  # ── 1. Column overview ──────────────────────
  cat("── COLUMN OVERVIEW ────────────────────────\n")
  
  for (col in names(df)) {
    x     <- df[[col]]
    cls   <- class(x)[1]
    n_na  <- sum(is.na(x))
    pct   <- round(n_na / n_row * 100, 1)
    n_uniq <- length(unique(x[!is.na(x)]))
    
    na_flag <- if (n_na == 0) "" else sprintf("  *** %d NAs (%.1f%%)", n_na, pct)
    cat(sprintf("  %-20s  %-12s  %d unique%s\n",
                col, cls, n_uniq, na_flag))
  }
  
  # ── 2. NA summary ────────────────────────────
  cat("\n── NA SUMMARY ─────────────────────────────\n")
  
  na_counts <- sapply(df, function(x) sum(is.na(x)))
  cols_with_na <- na_counts[na_counts > 0]
  
  if (length(cols_with_na) == 0) {
    cat("  No missing values. \n")
  } else {
    cat(sprintf("  Columns with NAs: %d of %d\n\n", length(cols_with_na), n_col))
    for (col in names(cols_with_na)) {
      bar_len  <- round(cols_with_na[col] / n_row * 20)
      bar      <- paste0(rep("█", bar_len), collapse = "")
      pct_na   <- round(cols_with_na[col] / n_row * 100, 1)
      cat(sprintf("  %-20s  [%-20s]  %d (%.1f%%)\n",
                  col, bar, cols_with_na[col], pct_na))
    }
  }
  
  # ── 3. Numeric columns ───────────────────────
  num_cols <- names(df)[sapply(df, is.numeric)]
  
  if (length(num_cols) > 0) {
    cat("\n── NUMERIC COLUMNS ────────────────────────\n")
    cat(sprintf("  %-20s  %8s  %8s  %8s  %8s  %8s\n",
                "Column", "Min", "Mean", "Median", "Max", "SD"))
    cat(sprintf("  %-20s  %8s  %8s  %8s  %8s  %8s\n",
                "──────", "───", "────", "──────", "───", "──"))
    
    for (col in num_cols) {
      x  <- df[[col]][!is.na(df[[col]])]
      if (length(x) == 0) next
      cat(sprintf("  %-20s  %8.3g  %8.3g  %8.3g  %8.3g  %8.3g\n",
                  col,
                  min(x), mean(x), median(x), max(x), sd(x)))
    }
  }
  
  # ── 4. Factor / character columns ───────────
  cat_cols <- names(df)[sapply(df, function(x) is.factor(x) | is.character(x))]
  
  if (length(cat_cols) > 0) {
    cat("\n── CATEGORICAL COLUMNS ────────────────────\n")
    
    for (col in cat_cols) {
      x    <- df[[col]]
      tbl  <- sort(table(x, useNA = "no"), decreasing = TRUE)
      n_lv <- length(tbl)
      cat(sprintf("\n  %s  (%d unique values)\n", col, n_lv))
      
      show <- min(top_n_levels, n_lv)
      for (i in seq_len(show)) {
        lbl <- names(tbl)[i]
        cnt <- tbl[i]
        pct <- round(cnt / n_row * 100, 1)
        cat(sprintf("    %-25s  %5d  (%.1f%%)\n", lbl, cnt, pct))
      }
      if (n_lv > top_n_levels) {
        cat(sprintf("    ... and %d more levels\n", n_lv - top_n_levels))
      }
    }
  }
  
  # ── 5. Duplicate rows ────────────────────────
  cat("\n── DUPLICATES ─────────────────────────────\n")
  n_dup <- sum(duplicated(df))
  if (n_dup == 0) {
    cat("  No duplicate rows.\n")
  } else {
    cat(sprintf("  %d duplicate row(s) found (%.1f%% of data)\n",
                n_dup, n_dup / n_row * 100))
  }
  
  cat("\n══════════════════════════════════════════\n")
  cat("  END OF REPORT\n")
  cat("══════════════════════════════════════════\n")
  
  # Return invisibly for programmatic use
  invisible(list(
    dims       = c(rows = n_row, cols = n_col),
    na_counts  = na_counts,
    n_dupes    = n_dup
  ))
}


## Файл: scripts/scaffold_analysis.R


#!/usr/bin/env Rscript
# scaffold_analysis.R — Generates a starter analysis script
#
# Usage (from terminal):
#   Rscript scaffold_analysis.R myproject
#   Rscript scaffold_analysis.R myproject outcome_var group_var
#
# Usage (from R console):
#   source("scaffold_analysis.R")
#   scaffold_analysis("myproject", outcome = "score", group = "treatment")
#
# Output: myproject_analysis.R  (ready to edit)

scaffold_analysis <- function(project_name,
                               outcome   = "outcome",
                               group     = "group",
                               data_file = NULL) {
  
  if (is.null(data_file)) data_file <- paste0(project_name, ".csv")
  out_file <- paste0(project_name, "_analysis.R")
  
  template <- sprintf(
'# ============================================================
# Project : %s
# Created : %s
# ============================================================

# ── 0. Libraries ─────────────────────────────────────────────
# Add packages you need here
# library(ggplot2)
# library(haven)     # for .dta files
# library(openxlsx)  # for Excel output


# ── 1. Load Data ─────────────────────────────────────────────
df <- read.csv("%s", stringsAsFactors = FALSE)

# Quick check — always do this first
cat("Dimensions:", dim(df), "\\n")
str(df)
head(df)


# ── 2. Explore / EDA ─────────────────────────────────────────
summary(df)

# NA check
na_counts <- colSums(is.na(df))
na_counts[na_counts > 0]

# Key variable distributions
hist(df$%s, main = "Distribution of %s", xlab = "%s")

if ("%s" %%in%% names(df)) {
  table(df$%s)
  barplot(table(df$%s),
          main = "Counts by %s",
          col  = "steelblue",
          las  = 2)
}


# ── 3. Clean / Transform ──────────────────────────────────────
# df <- df[complete.cases(df), ]        # drop rows with any NA
# df$%s <- as.factor(df$%s)            # convert to factor


# ── 4. Analysis ───────────────────────────────────────────────

# Descriptive stats by group
tapply(df$%s, df$%s, mean, na.rm = TRUE)
tapply(df$%s, df$%s, sd,   na.rm = TRUE)

# t-test (two groups)
# t.test(%s ~ %s, data = df)

# Linear model
fit <- lm(%s ~ %s, data = df)
summary(fit)
confint(fit)

# ANOVA (multiple groups)
# fit_aov <- aov(%s ~ %s, data = df)
# summary(fit_aov)
# TukeyHSD(fit_aov)


# ── 5. Visualize Results ──────────────────────────────────────
par(mfrow = c(1, 2))

# Boxplot by group
boxplot(%s ~ %s,
        data = df,
        main = "%s by %s",
        xlab = "%s",
        ylab = "%s",
        col  = "lightyellow")

# Model diagnostics
plot(fit, which = 1)  # residuals vs fitted

par(mfrow = c(1, 1))


# ── 6. Save Output ────────────────────────────────────────────
# Save cleaned data
# write.csv(df, "%s_clean.csv", row.names = FALSE)

# Save model summary to text
# sink("%s_results.txt")
# summary(fit)
# sink()

# Save plot to file
# png("%s_boxplot.png", width = 800, height = 600, res = 150)
# boxplot(%s ~ %s, data = df, col = "lightyellow")
# dev.off()
',
    project_name,
    format(Sys.Date(), "%%Y-%%m-%%d"),
    data_file,
    # Section 2 — EDA
    outcome, outcome, outcome,
    group, group, group, group,
    # Section 3
    group, group,
    # Section 4
    outcome, group,
    outcome, group,
    outcome, group,
    outcome, group,
    outcome, group,
    outcome, group,
    # Section 5
    outcome, group,
    outcome, group,
    group, outcome,
    # Section 6
    project_name, project_name, project_name,
    outcome, group
  )
  
  writeLines(template, out_file)
  cat(sprintf("Created: %s\n", out_file))
  invisible(out_file)
}


# ── Run from command line ─────────────────────────────────────
if (!interactive()) {
  args <- commandArgs(trailingOnly = TRUE)
  
  if (length(args) == 0) {
    cat("Usage: Rscript scaffold_analysis.R <project_name> [outcome_var] [group_var]\n")
    cat("Example: Rscript scaffold_analysis.R myproject score treatment\n")
    quit(status = 1)
  }
  
  project <- args[1]
  outcome <- if (length(args) >= 2) args[2] else "outcome"
  group   <- if (length(args) >= 3) args[3] else "group"
  
  scaffold_analysis(project, outcome = outcome, group = group)
}


## Файл: README.md


# base-r-skill 

GitHub: https://github.com/iremaydas/base-r-skill

Навык Claude Code для программирования на базовом R.

---

## История

Я аспирантка по политологии, регулярно использую R, но никогда не назвала бы себя *специалистом по R*. Мне был нужен навык Claude Code для базового R — без tidyverse, без ggplot2, просто обычный R, — и я нигде не смогла его найти.

Поэтому я сделала его сама. В 11 вечера. Попросив Claude помочь мне создать навык для Claude. 

Если вы тоже каждый раз гуглите `how to drop NA rows in R`, это для вас. 🫶

---

## Что внутри

```
base-r/
├── SKILL.md                    # Основной файл навыка
├── references/                 # Подводные камни и неочевидное поведение
│   ├── data-wrangling.md       # Ловушки выбора подмножеств, семейство apply, merge, особенности factor
│   ├── modeling.md             # Синтаксис формул, lm/glm/aov/nls, optim
│   ├── statistics.md           # Проверка гипотез, распределения, кластеризация
│   ├── visualization.md        # par, layout, устройства, цвета
│   ├── io-and-text.md          # read.table, grep, регулярные выражения, format
│   ├── dates-and-system.md     # Ловушки Date/POSIXct, options(), операции с файлами
│   └── misc-utilities.md       # tryCatch, do.call, временные ряды, утилиты
├── scripts/
│   ├── check_data.R            # Краткий отчёт о качестве данных для любого фрейма данных
│   └── scaffold_analysis.R     # Генерирует стартовый скрипт анализа
└── assets/
    └── analysis_template.R     # Шаблон анализа для копирования и вставки
```

Справочные файлы были сокращены из официального руководства R 4.5.3 — **19,518 строк → 945 строк** (сокращение на 95%). Осталось только неочевидное: подводные камни, неожиданные значения по умолчанию, сложные взаимодействия. То, что Claude и так хорошо знает, было удалено.

---

## Как использовать

Добавьте этот навык в свою конфигурацию Claude Code, указав данный репозиторий. После этого Claude будет автоматически загружать соответствующие справочные файлы, когда вы работаете над задачами R.

Лучше всего подходит для:
- Преобразования данных в базовом R (без tidyverse)
- Статистического моделирования с `lm`, `glm`, `aov`
- Базовой графики с `plot`, `par`, `barplot`
- Понимания того, почему ваш код R делает что-то странное

Не подходит для: tidyverse, ggplot2, Shiny или разработки пакетов R.

---

## Скрипт `check_data.R`

Вероятно, самая полезная самостоятельная вещь здесь. Подключите его через source и выполните `check_data(df)` для любого фрейма данных, чтобы получить оформленный отчёт о размерностях, количестве NA, числовых сводках и разбивках категориальных данных.

```r
source("scripts/check_data.R")
check_data(your_df)
```

---

## Создано при помощи

- Claude (разумеется)
- Официальных руководств R (всех их 19,518 строк)
- Лёгкого раздражения и нескольких чашек кофе

---

## Участие в проекте

Если заметите пропущенный подводный камень, неправильное значение по умолчанию или что-то, что должно быть в справочниках, — PR очень приветствуются. Я тоже учусь.

---

*Создано [@iremaydas](https://github.com/iremaydas) — аспиранткой, время от времени пользующейся R и постоянно гуглящей вещи, которые мне, наверное, уже пора знать.*

---
Источник: prompts.chat. Текст: CC0 1.0 Universal. Русская версия: Kvantora.
