Какой алгоритм лежит в основе функции `split` ядра R?



(1)

Как работает 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 ?

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
}
  1. если x не имеет классов (т.е., в основном, атомарный вектор), используется .Internal(split(x, f)) ;
  2. в противном случае он использует .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)) ?

Это действительно ядро ​​ядра! Я возьму небольшой пример ниже для объяснения:

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
         ^

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 объясняющий, как split.data.frame , split.default и C-level .Internal(split(x, f)) . Другие ответы приветствуются на объекте «Date» и «POSIXct».





split