Какой алгоритм лежит в основе функции `split` ядра R? - PullRequest
0 голосов
/ 04 сентября 2018

split является особенно важной функцией в ядре R. Многие ответы Stack Overflow, предлагающие решения R-base по обработке данных, полагаются на него. Это рабочая лошадка любой групповой операции.

Есть также много вопросов, решение которых - всего одна строка с split. Многие люди не знают, что

  • split.data.frame может разбить матрицу на строку;
  • split.default может разбить фрейм данных по столбцам.

Возможно, документация R на split работает не очень хорошо. В нем упоминается первое использование, но не упоминается второе.

Существует четыре метода для split в ядре R:

methods(split)
#[1] split.data.frame split.Date       split.default    split.POSIXct

Я дам ответ, подробно объясняющий, как работают split.data.frame, split.default и уровень C .Internal(split(x, f)). Другие ответы приветствуются для объекта «Date» и «POSIXct».

1 Ответ

0 голосов
/ 04 сентября 2018

Как работает split.data.frame?

function (x, f, drop = FALSE, ...) 
lapply(split(x = seq_len(nrow(x)), f = f, drop = drop, ...), 
       function(ind) x[ind, , drop = FALSE])

Он вызывает split.default для разделения вектора индекса строки seq_len(nrow(x)), а затем использует цикл lapply для извлечения связанных строк в запись списка.

Это не совсем метод "data.frame". Разбивает любые 2-мерные объекты по 1-му измерению, включая разбиение матрицы по строкам .


Как работает split.default? 1016 * function (x, f, drop = FALSE, sep = ".", lex.order = FALSE, ...) { if (!missing(...)) .NotYetUsed(deparse(...), error = FALSE) if (is.list(f)) f <- interaction(f, drop = drop, sep = sep, lex.order = lex.order) else if (!is.factor(f)) f <- as.factor(f) else if (drop) f <- factor(f) storage.mode(f) <- "integer" if (is.null(attr(x, "class"))) return(.Internal(split(x, f))) lf <- levels(f) y <- vector("list", length(lf)) names(y) <- lf ind <- .Internal(split(seq_along(x), f)) for (k in lf) y[[k]] <- x[ind[[k]]] y } , если x не имеет классов (т.е., в основном, атомарный вектор), используется .Internal(split(x, f)); в противном случае он использует .Internal(split()) для разделения индекса по x, затем использует цикл for для извлечения связанных элементов в элемент списка. Атомный вектор (см. ?vector) - это вектор со следующим режимом: «логический», «целое число», «числовой», «сложный», «символ» и «необработанный» "список" "выражение" Объект с классом ... Э-э ... их так много! Позвольте мне привести три примера: "фактор" "data.frame" "матрица" На мой взгляд, split.default написано не очень хорошо. Есть так много объектов с классами, но split.default будет обрабатывать их так же, как "[". Это прекрасно работает с «factor» и «data.frame» (поэтому мы будем разбивать фрейм данных по столбцам!), Но определенно не будет работать с матрицей так, как мы ожидаем. A <- matrix(1:9, 3) # [,1] [,2] [,3] #[1,] 1 4 7 #[2,] 2 5 8 #[3,] 3 6 9 split.default(A, c(1, 1, 2)) ## it does not split the matrix by columns! #$`1` #[1] 1 2 4 5 7 8 # #$`2` #[1] 3 6 9 На самом деле правило утилизации было применено к c(1, 1, 2), и мы эквивалентно делаем: split(c(A), rep_len(c(1,1,2), length(A))) Почему R core не пишет еще одну строку для "матрицы", как for (k in lf) y[[k]] <- x[, ind[[k]], drop = FALSE] До сих пор единственный способ безопасно разделить матрицу по столбцам - это транспонировать ее, затем split.data.frame, затем другую транспонирование. lapply(split.data.frame(t(A), c(1, 1, 2)), t) Другой обходной путь через lapply(split.default(data.frame(A), c(1, 1, 2)), as.matrix) глючит, если A - это матрица символов. Как работает .Internal(split(x, f))? 1074 * Это действительно ядро ​​ядра! Ниже я приведу небольшой пример для объяснения: set.seed(0) f <- sample(factor(letters[1:3]), 10, TRUE) # [1] c a b b c a c c b b #Levels: a b c x <- 0:9 В основном есть 3 шага. Для улучшения читабельности для каждого шага предоставляется эквивалентный R-код. шаг 1: табулирование (подсчет появления каждого факторного уровня) ## a factor has integer mode so `tabulate` works tab <- tabulate(f, nbins = nlevels(f)) [1] 2 4 4 шаг 2: распределение памяти результирующего списка result <- vector("list", nlevels(f)) for (i in 1:length(tab)) result[[i]] <- vector(mode(x), tab[i]) names(result) <- levels(f) Я бы прокомментировал этот список следующим образом, где каждая строка является элементом списка, который в этом примере является вектором, а каждая [ ] является заполнителем для записи этого вектора. $a: [ ] [ ] $b: [ ] [ ] [ ] [ ] $c: [ ] [ ] [ ] [ ] шаг 3: распределение элементов Теперь полезно раскрыть внутренний целочисленный режим для множителя: .f <- as.integer(f) #[1] 3 1 2 2 3 1 3 3 2 2 Нам нужно сканировать x и .f, заполняя x[i] в правильную запись из result[[.f[i]]], сообщаемую вектором буфера накопителя. ab <- integer(nlevels(f)) ## accumulator buffer for (i in 1:length(.f)) { fi <- .f[i] counter <- ab[fi] + 1L result[[fi]][counter] <- x[i] ab[fi] <- counter } На следующем рисунке ^ - указатель на элементы, к которым осуществляется доступ или которые обновляются. ## i = 1 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [0] [0] [0] ## on entry ^ $a: [ ] [ ] $b: [ ] [ ] [ ] [ ] $c: [0] [ ] [ ] [ ] ^ ab: [0] [0] [1] ## on exit ^ ## i = 2 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [0] [0] [1] ## on entry ^ $a: [1] [ ] ^ $b: [ ] [ ] [ ] [ ] $c: [0] [ ] [ ] [ ] ab: [1] [0] [1] ## on exit ^ ## i = 3 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [1] [0] [1] ## on entry ^ $a: [1] [ ] $b: [2] [ ] [ ] [ ] ^ $c: [0] [ ] [ ] [ ] ab: [1] [1] [1] ## on exit ^ ## i = 4 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [1] [1] [1] ## on entry ^ $a: [1] [ ] $b: [2] [3] [ ] [ ] ^ $c: [0] [ ] [ ] [ ] ab: [1] [2] [1] ## on exit ^ ## i = 5 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [1] [2] [1] ## on entry ^ $a: [1] [ ] $b: [2] [3] [ ] [ ] $c: [0] [4] [ ] [ ] ^ ab: [1] [2] [2] ## on exit ^ ## i = 6 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [1] [2] [2] ## on entry ^ $a: [1] [5] ^ $b: [2] [3] [ ] [ ] $c: [0] [4] [ ] [ ] ab: [2] [2] [2] ## on exit ^ ## i = 7 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [2] [2] [2] ## on entry ^ $a: [1] [5] $b: [2] [3] [ ] [ ] $c: [0] [4] [6] [ ] ^ ab: [2] [2] [3] ## on exit ^ ## i = 8 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [2] [2] [3] ## on entry ^ $a: [1] [5] $b: [2] [3] [ ] [ ] $c: [0] [4] [6] [7] ^ ab: [2] [2] [4] ## on exit ^ ## i = 9 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [2] [2] [4] ## on entry ^ $a: [1] [5] $b: [2] [3] [8] [ ] ^ $c: [0] [4] [6] [7] ab: [2] [3] [4] ## on exit ^ ## i = 10 x: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] .f: [3] [1] [2] [2] [3] [1] [3] [3] [2] [2] ^ ab: [2] [3] [4] ## on entry ^ $a: [1] [5] $b: [2] [3] [8] [9] ^ $c: [0] [4] [6] [7] ab: [2] [4] [4] ## on exit ^

...