Должен ли я использовать data.frame или матрицу?
Когда следует использовать data.frame
и когда лучше использовать matrix
?
Оба хранят данные в прямоугольном формате, поэтому иногда это неясно.
Существуют ли общие правила, когда следует использовать какой тип данных?
7 ответов
Часть ответа уже содержится в вашем вопросе: вы используете фреймы данных, если можно ожидать, что столбцы (переменные) будут разных типов (числовые / символьные / логические и т. Д.). Матрицы предназначены для данных одного типа.
Следовательно, выбор matrix/data.frame проблематичен только в том случае, если у вас есть данные одного типа.
Ответ зависит от того, что вы собираетесь делать с данными в data.frame/matrix. Если оно будет передано другим функциям, то ожидаемый тип аргументов этих функций определяет выбор.
Также:
Матрицы более эффективны по памяти:
m = matrix(1:4, 2, 2)
d = as.data.frame(m)
object.size(m)
# 216 bytes
object.size(d)
# 792 bytes
Матрицы необходимы, если вы планируете выполнять какие-либо операции типа линейной алгебры.
Фреймы данных более удобны, если вы часто ссылаетесь на их столбцы по имени (через компактный оператор $).
Фреймы данных также ИМХО лучше подходят для представления (печати) табличной информации, поскольку вы можете применять форматирование к каждому столбцу отдельно.
@Michal не упомянул о том, что матрица не только меньше, чем эквивалентный фрейм данных, но использование матриц может сделать ваш код гораздо более эффективным, чем использование фреймов данных, зачастую значительно. Это одна из причин, почему внутренне многие функции R будут приводить к данным матриц, которые находятся во фреймах данных.
Фреймы данных часто намного удобнее; не всегда есть только атомарные порции данных.
Обратите внимание, что у вас может быть матрица символов; вам не нужно просто иметь числовые данные для построения матрицы в R.
При преобразовании фрейма данных в матрицу обратите внимание, что существует data.matrix()
функция, которая обрабатывает факторы соответствующим образом путем преобразования их в числовые значения на основе внутренних уровней. Принуждение через as.matrix()
приведет к матрице символов, если любая из меток фактора не является числовой. Для сравнения:
> head(as.matrix(data.frame(a = factor(letters), B = factor(LETTERS))))
a B
[1,] "a" "A"
[2,] "b" "B"
[3,] "c" "C"
[4,] "d" "D"
[5,] "e" "E"
[6,] "f" "F"
> head(data.matrix(data.frame(a = factor(letters), B = factor(LETTERS))))
a B
[1,] 1 1
[2,] 2 2
[3,] 3 3
[4,] 4 4
[5,] 5 5
[6,] 6 6
Я почти всегда использую фрейм данных для своих задач анализа данных, поскольку у меня часто есть больше, чем просто числовые переменные. Когда я кодирую функции для пакетов, я почти всегда приводю к матрице, а затем форматирую результаты обратно как фрейм данных. Это потому, что фреймы данных удобны.
@Michal: матрицы на самом деле не более эффективны для памяти:
m <- matrix(1:400000, 200000, 2)
d <- data.frame(m)
object.size(m)
# 1600200 bytes
object.size(d)
# 1600776 bytes
... если у вас нет большого количества столбцов:
m <- matrix(1:400000, 2, 200000)
d <- data.frame(m)
object.size(m)
# 1600200 bytes
object.size(d)
# 22400568 bytes
Матрица на самом деле является вектором с дополнительными методами. пока data.frame - это список. Разница заключается в том, что вектор против списка. для эффективности вычислений придерживайтесь матрицы. Использование data.frame, если вам нужно.
Я не могу больше подчеркнуть разницу в эффективности между ними! Хотя это правда, что DF более удобны в некоторых случаях анализа данных, они также допускают разнородные данные, и некоторые библиотеки принимают их только, но все это действительно вторично, если вы не пишете одноразовый код для конкретной задачи.
Позвольте привести пример. Была функция, которая вычисляла бы 2D путь метода MCMC. По сути, это означает, что мы берем начальную точку (x,y) и повторяем определенный алгоритм, чтобы найти новую точку (x,y) на каждом шаге, таким образом создавая весь путь. Алгоритм включает в себя вычисление довольно сложной функции и генерацию некоторой случайной величины на каждой итерации, поэтому, когда она запускалась в течение 12 секунд, я думала, что это нормально, учитывая, сколько вещей она делает на каждом шаге. При этом функция собрала все точки в построенном пути вместе со значением целевой функции в 3-столбцовом data.frame. Таким образом, 3 столбца не так велики, и количество шагов также было более чем разумным 10000 (в этом типе проблем типичны пути длиной 1 000 000, поэтому 10 000 - это ничто). Итак, я думал, что DF 10,000x3 определенно не проблема. Причина использования DF проста. После вызова функции был вызван ggplot(), чтобы нарисовать результирующий (x,y)-path. И ggplot() не принимает матрицу.
Затем в какой-то момент из любопытства я решил изменить функцию, чтобы собирать путь в матрице. Радостно, что синтаксис DF и матриц похож, все, что я сделал, это изменил строку, указав df как data.frame, на строку, инициализировав ее как матрицу. Здесь я также должен упомянуть, что в исходном коде DF был инициализирован, чтобы иметь окончательный размер, поэтому позже в коде функции только новые значения были записаны в уже выделенные пробелы, и не было никаких дополнительных затрат на добавление новых строк в DF. Это делает сравнение еще более справедливым, а также упрощает мою работу, поскольку мне не нужно было ничего переписывать в функции. Изменение только одной строки от первоначального размещения data.frame требуемого размера к матрице того же размера. Чтобы адаптировать новую версию функции к ggplot(), я преобразовал теперь возвращаемую матрицу в data.frame для использования в ggplot().
После повторного запуска кода я не мог поверить в результат. Код запускается за доли секунды! Вместо примерно 12 секунд. И снова, функция в течение 10 000 итераций считывает и записывает значения только в уже выделенные пространства в DF (а теперь и в матрице). И эта разница также для разумного (или, скорее, небольшого) размера 10000x3.
Итак, если ваша единственная причина использовать DF - сделать его совместимым с библиотечной функцией, такой как ggplot(), вы всегда можете преобразовать ее в DF в последний момент - работайте с матрицами, насколько вам удобно. Если, с другой стороны, есть более существенная причина для использования DF, например, вы используете какой-то пакет для анализа данных, который в противном случае потребовал бы постоянного преобразования из матриц в DF и обратно, или вы не выполняете интенсивные вычисления самостоятельно и используете только стандартные пакеты (многие из них на самом деле внутренне преобразуют DF в матрицу, выполняют свою работу, а затем преобразуют результат обратно - так, что они выполняют всю эффективную работу за вас), или выполняют разовую работу, чтобы вам было все равно удобнее с DF, тогда вам не стоит беспокоиться об эффективности.
Или другое более практичное правило: если у вас есть вопрос, такой как в OP, используйте матрицы, поэтому вы будете использовать DF только тогда, когда у вас нет такого вопроса (потому что вы уже знаете, что должны использовать DF, или потому что вы делаете не очень важно, так как код разовый и т. д.).
Но в целом всегда помните об этой точке эффективности как о приоритетной.
Матрицы и фреймы данных представляют собой прямоугольные двумерные массивы и могут быть неоднородными по строкам и столбцам. Они разделяют некоторые методы и свойства, но не все.
Примеры:
M <- list(3.14,TRUE,5L,c(2,3,5),"dog",1i) # a list
dim(M) <- c(2,3) # set dimensions
print(M) # print result
# [,1] [,2] [,3]
# [1,] 3.14 5 "dog"
# [2,] TRUE Numeric,3 0+1i
DF <- data.frame(M) # a data frame
print(DF) # print result
# X1 X2 X3
# 1 3.14 5 dog
# 2 TRUE 2, 3, 5 0+1i
M <- matrix(c(1,1,1,1,2,3,1,3,6),3) # a numeric matrix
DF <- data.frame(M) # a all numeric data frame
solve(M) # obtains inverse matrix
solve(DF) # obtains inverse matrix
det(M) # obtains determinant
det(DF) # error
Вот интересный результат. Работа с матрицами по сравнению с тибблами происходит быстрее, но разница уменьшается по мере увеличения матрицы. Обратите внимание, что накладные расходы на преобразование тиббла в матрицу больше, чем на преобразование тиббла в матрицу. В широком смысле работа исключительно (без приведения типов) в матричном пространстве происходит примерно на 20 % быстрее, чем работа в пространстве dplyr.
library(dplyr)
# big (?) matrix
m <- matrix(runif(50000), 10000, 5, dimnames =list(NULL,c(letters[1:5])))
d <- as_tibble(m)
# return a matrix, convert tibble to matrix after operation
bench::mark(
zm <- apply(m, 2, FUN = \(x) cumprod(1+x)*100),
zd <- d |>
mutate(across(everything(),.fns = \(x) cumprod(1+x)*100)) |>
as.matrix()
)
#> # A tibble: 2 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:> <bch:> <dbl> <bch:byt> <dbl>
#> 1 zm <- apply(m, 2, FUN = function(x… 5.49ms 5.66ms 176. 2.52MB 8.67
#> 2 zd <- as.matrix(mutate(d, across(e… 6.9ms 7.05ms 140. 3.54MB 4.17
# return a tibble. Convert matrix to tibble after operation
bench::mark(
zm <- as_tibble(apply(m, 2, FUN = \(x) cumprod(1+x)*100)),
zd <- d |>
mutate(across(everything(),.fns = \(x) cumprod(1+x)*100))
)
#> # A tibble: 2 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:> <bch:> <dbl> <bch:byt> <dbl>
#> 1 zm <- as_tibble(apply(m, 2, FUN = … 5.71ms 5.92ms 167. 2.87MB 11.7
#> 2 zd <- mutate(d, across(everything(… 6.72ms 6.85ms 144. 788.62KB 4.18
# small matrix
m <- matrix(runif(500), 100, 5, dimnames =list(NULL,c(letters[1:5])))
d <- as_tibble(m,.name_repair = "unique")
# return a matrix
bench::mark(
zm <- apply(m, 2, FUN = \(x) cumprod(1+x)*100),
zd <- d |>
mutate(across(everything(),.fns = \(x) cumprod(1+x)*100)) |>
as.matrix()
)
#> # A tibble: 2 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:t> <bch:t> <dbl> <bch:byt> <dbl>
#> 1 zm <- apply(m, 2, FUN = function… 24.1µs 27.2µs 29303. 35.8KB 17.6
#> 2 zd <- as.matrix(mutate(d, across… 1.69ms 1.79ms 548. 19.2KB 17.6
# return a tibble
bench::mark(
zm <- as_tibble(apply(m, 2, FUN = \(x) cumprod(1+x)*100)),
zd <- d |>
mutate(across(everything(),.fns = \(x) cumprod(1+x)*100))
)
#> # A tibble: 2 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
#> 1 zm <- as_tibble(apply(m, 2, FU… 111.3µs 126.1µs 7586. 39.9KB 19.2
#> 2 zd <- mutate(d, across(everyth… 1.63ms 1.72ms 565. 15.2KB 17.1
Создано 3 июля 2023 г. с использованием reprex v2.0.2.