Всё в R — вектор
В большинстве языков есть скаляры — одиночные значения — и какой-нибудь контейнерный тип для хранения нескольких. R пропускает скаляр. Вектор — это упорядоченная коллекция значений одного типа, и это та единица, на которой построено всё остальное. Даже то, что выглядит как одинокое число, — вектор длины 1:
c() — сокращение от combine — это то, чем вы собираете вектор вручную. length() сообщает, сколько в нём элементов. А 5 само по себе даёт is.vector равным TRUE и длину 1: R никогда не перестаёт работать с векторами, просто иногда работает с очень короткими.
Из-за этой архитектуры код на R выглядит иначе, чем на Python или JavaScript: операции, для которых в других местах пишут цикл, применяются к целым векторам одним выражением. К этому мы вернёмся ниже.
Одно правило стоит усвоить прямо сейчас: все элементы вектора имеют один тип. Если передать c() смесь, он не пожалуется — он молча приведёт всё к самому гибкому из присутствующих типов:
Строка в смеси превращает всё в строки ("1", "TRUE"). Логические среди чисел становятся 1 и 0. Это приведение типов следует фиксированной иерархии — logical → integer → double → character — и происходит без предупреждения; поэтому затесавшаяся "7" в столбце чисел может тихо превратить весь ваш набор данных в текст. Полная иерархия разобрана в статье про типы данных; если нужно хранить действительно разнородные значения, для этого есть списки.
Генерация последовательностей: :, seq() и rep()
Набирать каждый элемент в c() быстро надоедает. В R есть три сокращения для регулярных шаблонов:
1:10— повседневный вариант: целые числа от одной границы до другой с шагом 1.seq()обобщает его: задайте шаг черезby =или скажите, сколько элементов вам нужно, черезlength.out =, и пусть R сам вычислит расстояние.seq_len(n)даёт числа от1доnи является безопасным способом строить индексы цикла: в отличие от1:n, он корректно возвращает пустой вектор приn, равном 0, а не считает1 0в обратную сторону.rep()повторяет:times =повторяет весь вектор целиком (1 2 1 2 1 2),each =повторяет каждый элемент на месте (1 1 1 2 2 2). Их легко перепутать; запустите сниппет и сравните.
Индексация: R считает с 1
Квадратные скобки извлекают элементы. Первый элемент — [1], а не [0]. Если вы пришли из Python или JavaScript, это самая частая ранняя оплошность:
Три вещи здесь заслуживают паузы:
- Индексировать можно вектором позиций —
fruits[c(2, 4)]берёт второй и четвёртый за один раз. - Отрицательные индексы означают «всё, кроме».
fruits[-1]— это вектор без первого элемента. Это совершенно не то же самое, что в Python, где-1означает последний элемент. Смешивать положительные и отрицательные индексы в одном вызове нельзя. fruits[0]— не ошибка; он возвращает пустойcharacter(0). Ошибка на единицу в R часто даёт молчаливо пустой результат, а не падение, поэтому стоит проверятьlength(), когда что-то дальше по коду загадочно пустует.
Элементы могут также нести имена, что даёт третий способ индексации — по метке:
Именованные векторы работают как лёгкая таблица соответствий и делают код читаемым: prices["tea"] говорит, что имеется в виду, а prices[2] — нет.
Логические маски и which()
Самый мощный способ индексации — логический вектор, маска из значений TRUE/FALSE той же длины, что и данные. Любое сравнение над вектором даёт ровно её:
temps > 24 даёт не один ответ, а пять — по одному на элемент. Поместив эту маску в [ ], вы оставляете элементы там, где маска равна TRUE. Комбинируйте условия через & (и) и | (или) — они разобраны вместе с остальными операторами.
which() переводит маску в позиции: здесь 2 3 5 — индексы жарких измерений. Берите её, когда нужно знать, где находятся совпадения, — чтобы сообщить о них или проиндексировать по тем же местам другой вектор. Для простой фильтрации temps[temps > 24] прямее, чем temps[which(temps > 24)].
Этот шаблон «фильтрация маской» — хребет практически всей работы с данными в R; он возвращается ровно в том же виде, когда вы фильтруете строки датафрейма.
Векторная арифметика и правило переработки
Арифметика в R применяется поэлементно к целым векторам — цикл не нужен:
Первая строка — главная особенность: prices * 2 удваивает каждый элемент. Там, где другим языкам нужен цикл for, R нужен оператор — и векторизованная форма и короче, и быстрее, потому что цикл выполняется в оптимизированном C под капотом. Пишите цикл for, когда каждый шаг зависит от предыдущего; для поэлементной математики векторизуйте.
Третья строка показывает правило переработки: когда длины векторов различаются, R повторяет более короткий, чтобы сравнять их. c(10, 20) перерабатывается в 10 20 10 20, давая 11 22 13 24. Переработка 2 по вектору длины 3 — это ровно то, благодаря чему сработало prices * 2: скаляр — это просто перерабатываемый вектор длины 1.
Переработка чиста, когда большая длина точно кратна меньшей. Когда это не так, R всё равно вычисляет ответ — и выдаёт предупреждение:
Вы получите 11 22 13 плюс предупреждение о том, что длина более длинного объекта не кратна длине более короткого. Относитесь к этому предупреждению как к отчёту об ошибке: частичная переработка почти никогда не является тем, что вы имели в виду, и обычно означает, что два вектора, которые должны были быть одинаковой длины, таковыми не оказались.
Сортировка, разворот, удаление дубликатов
Три маленькие утилиты, которыми вы будете пользоваться постоянно:
sort() упорядочивает значения, rev() переворачивает существующий порядок без сортировки, а unique() отбрасывает дубликаты, сохраняя первые появления. Ни одна из них не изменяет x — как почти всё в R, они возвращают новый вектор и оставляют оригинал в покое.
Что вы уносите с собой
- Вектор — фундаментальная единица R: упорядоченная, одного типа насквозь, и даже одиночные значения являются векторами длины 1.
- Стройте через
c(), генерируйте шаблоны через:,seq()иrep()— и помните, чтоc()молча приводит смешанные типы. - Индексация начинается с 1; отрицательные индексы выбрасывают элементы; имена позволяют индексировать по метке.
- Логические маски (
x[x > 5]) — идиома фильтрации всего языка;which()превращает маску в позиции. - Арифметика векторизована, короткие векторы перерабатываются, а предупреждение о частичной переработке означает, что у вас баг.
Дальше: списки — контейнер для случая, когда ваши значения не все одного типа.
Часто задаваемые вопросы
Как создать вектор в R?
С помощью функции c() (от combine): x <- c(10, 20, 30). Для регулярных последовательностей используйте 1:10, seq(0, 1, by = 0.25) или rep(0, 5). Все элементы в итоге должны быть одного типа — если смешать типы, R молча приведёт их все к самому гибкому.
Индексация в R начинается с 0 или с 1?
С 1. x[1] — первый элемент, x[length(x)] — последний. Это сбивает с толку всех, кто пришёл из Python, JavaScript или C: элемента x[0] не существует (запрос вернёт пустой вектор, а не ошибку, из-за чего баг остаётся тихим).
Что делает правило переработки в R?
Когда вы выполняете арифметику над двумя векторами разной длины, R повторяет более короткий, пока он не сравняется с длинным. c(1, 2, 3, 4) + c(10, 20) даёт 11 22 13 24. Если большая длина не кратна меньшей, R всё равно вычислит ответ, но выдаст предупреждение — относитесь к нему как к сообщению об ошибке.
Что делает which() в R?
Она превращает логический вектор в позиции его значений TRUE. Если x > 5 даёт FALSE TRUE TRUE, то which(x > 5) даёт 2 3. Используйте её, когда нужны сами позиции; для простой фильтрации проще индексировать логическим вектором напрямую (x[x > 5]).