arrays - Методология структурирования высокоразмерных данных в R и MATLAB




multidimensional-array methodology (2)

Может быть dplyr :: tbl_cube?

Работая над отличным ответом @ BrodieG, я считаю, что вам может быть полезно взглянуть на новые функциональные возможности, доступные из dplyr::tbl_cube . Это, по сути, многомерный объект, который вы можете легко создать из списка массивов (как вы сейчас используете), который имеет некоторые действительно хорошие функции для подмножества, фильтрации и подведения итогов, которые (что важно, я думаю,) последовательно используются в " куб "и" табличный "вид данных.

require(dplyr)

Пара предостережений:

Это ранний выпуск: все проблемы, которые идут вместе с этим
Рекомендуется, чтобы эта версия выгружала plyr при загрузке dplyr

Загрузка массивов в кубы

Вот пример использования arr как определено в другом ответе:

# using arr from previous example
# we can convert it simply into a tbl_cube
arr.cube<-as.tbl_cube(arr)

arr.cube  
#Source: local array [24 x 3]  
#D: ser [chr, 3]  
#D: smp [chr, 2]  
#D: tr [chr, 4]  
#M: arr [dbl[3,2,4]]

Итак, обратите внимание, что D означает «Размеры и М меры», и вы можете иметь столько, сколько хотите.

Легкое преобразование от многомерного к плоским

Вы можете легко сделать данные таблицами, возвратив их как data.frame (которые вы можете просто преобразовать в таблицу данных, если вам нужны функциональные возможности и преимущества производительности позже)

head(as.data.frame(arr.cube))
#    ser   smp   tr       arr
#1 ser 1 smp 1 tr 1 0.6656456
#2 ser 2 smp 1 tr 1 0.6181301
#3 ser 3 smp 1 tr 1 0.7335676
#4 ser 1 smp 2 tr 1 0.9444435
#5 ser 2 smp 2 tr 1 0.8977054
#6 ser 3 smp 2 tr 1 0.9361929

Подменю

Вы могли бы сгладить все данные для каждой операции, но это имеет много последствий для производительности и полезности. Я считаю, что реальная выгода от этого пакета заключается в том, что вы можете «предварительно разбить» куб для данных, которые вам нужны, прежде чем преобразовывать их в табличный формат, который является ggplot-friendly, например простая фильтрация, чтобы возвращать только последовательность 1:

arr.cube.filtered<-filter(arr.cube,ser=="ser 1")
as.data.frame(arr.cube.filtered)
#    ser   smp   tr       arr
#1 ser 1 smp 1 tr 1 0.6656456
#2 ser 1 smp 2 tr 1 0.9444435
#3 ser 1 smp 1 tr 2 0.4331116
#4 ser 1 smp 2 tr 2 0.3916376
#5 ser 1 smp 1 tr 3 0.4669228
#6 ser 1 smp 2 tr 3 0.8942300
#7 ser 1 smp 1 tr 4 0.2054326
#8 ser 1 smp 2 tr 4 0.1006973

В настоящее время tbl_cube работает с функциями dplyr summarise() , select() , group_by() и filter() . Целесообразно вы можете связать их вместе с оператором %.% .

В остальных примерах я собираюсь использовать встроенный объект nasa tbl_cube, который содержит кучу метеорологических данных (и демонстрирует множество измерений и мер):

Групповые и итоговые меры

nasa
#Source: local array [41,472 x 4]
#D: lat [dbl, 24]
#D: long [dbl, 24]
#D: month [int, 12]
#D: year [int, 6]
#M: cloudhigh [dbl[24,24,12,6]]
#M: cloudlow [dbl[24,24,12,6]]
#M: cloudmid [dbl[24,24,12,6]]
#M: ozone [dbl[24,24,12,6]]
#M: pressure [dbl[24,24,12,6]]
#M: surftemp [dbl[24,24,12,6]]
#M: temperature [dbl[24,24,12,6]]

Итак, вот пример, показывающий, как легко отбросить подмножество модифицированных данных из куба, а затем сгладить его так, чтобы оно было подходящим для построения:

plot_data<-as.data.frame(          # as.data.frame so we can see the data
filter(nasa,long<(-70)) %.%        # filter long < (-70) (arbitrary!)
group_by(lat,long) %.%             # group by lat/long combo
summarise(p.max=max(pressure),     # create summary measures for each group
          o.avg=mean(ozone),
          c.all=(cloudhigh+cloudlow+cloudmid)/3)
)

head(plot_data)

#       lat   long p.max    o.avg    c.all
#1 36.20000 -113.8   975 310.7778 22.66667
#2 33.70435 -113.8   975 307.0833 21.33333
#3 31.20870 -113.8   990 300.3056 19.50000
#4 28.71304 -113.8  1000 290.3056 16.00000
#5 26.21739 -113.8  1000 282.4167 14.66667
#6 23.72174 -113.8  1000 275.6111 15.83333

Согласованная нотация для структур данных nd и 2-d

К сожалению, функция mutate() еще не реализована для tbl_cube но похоже, что это будет просто (не так много). Вы можете использовать его (и все другие функции, которые работают на кубе) по табулярному результату, хотя и с точно такой же нотацией. Например:

plot_data.mod<-filter(plot_data,lat>25) %.%    # filter out lat <=25
mutate(arb.meas=o.avg/p.max)                   # make a new column

head(plot_data.mod)

#       lat      long p.max    o.avg    c.all  arb.meas
#1 36.20000 -113.8000   975 310.7778 22.66667 0.3187464
#2 33.70435 -113.8000   975 307.0833 21.33333 0.3149573
#3 31.20870 -113.8000   990 300.3056 19.50000 0.3033389
#4 28.71304 -113.8000  1000 290.3056 16.00000 0.2903056
#5 26.21739 -113.8000  1000 282.4167 14.66667 0.2824167
#6 36.20000 -111.2957   930 313.9722 20.66667 0.3376045

Plotting - как пример функциональности R, которая «любит» плоские данные

Затем вы можете построить график с помощью ggplot() используя преимущества сглаженных данных:

# plot as you like:
ggplot(plot_data.mod) +
  geom_point(aes(lat,long,size=c.all,color=c.all,shape=cut(p.max,6))) +
  facet_grid( lat ~ long ) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

Использование таблицы данных по полученным плоским данным

Я не собираюсь расширять использование data.table здесь, так как это хорошо сделано в предыдущем ответе. Очевидно, есть много веских причин использовать data.table - для любой ситуации здесь вы можете вернуть один путем простого преобразования data.frame:

data.table(as.data.frame(your_cube_name))

Динамическая работа с вашим кубом

Еще одна вещь, которую я считаю превосходной, - это возможность добавлять меры (срезы / сценарии / смены, что бы вы хотели назвать) к вашему кубу. Я думаю, что это будет хорошо соответствовать методу анализа, описанному в вопросе. Вот простой пример с arr.cube - добавление дополнительной меры, которая сама является (по общему признанию, простой) функцией предыдущей меры. Вы получаете доступ / обновляете меры через синтаксис yourcube $mets[$...]

head(as.data.frame(arr.cube))

#    ser   smp   tr       arr
#1 ser 1 smp 1 tr 1 0.6656456
#2 ser 2 smp 1 tr 1 0.6181301
#3 ser 3 smp 1 tr 1 0.7335676
#4 ser 1 smp 2 tr 1 0.9444435
#5 ser 2 smp 2 tr 1 0.8977054
#6 ser 3 smp 2 tr 1 0.9361929

arr.cube$mets$arr.bump<-arr.cube$mets$arr*1.1  #arb modification!

head(as.data.frame(arr.cube))

#    ser   smp   tr       arr  arr.bump
#1 ser 1 smp 1 tr 1 0.6656456 0.7322102
#2 ser 2 smp 1 tr 1 0.6181301 0.6799431
#3 ser 3 smp 1 tr 1 0.7335676 0.8069244
#4 ser 1 smp 2 tr 1 0.9444435 1.0388878
#5 ser 2 smp 2 tr 1 0.8977054 0.9874759
#6 ser 3 smp 2 tr 1 0.9361929 1.0298122

Размеры - или нет ...

Я немного поиграл с попыткой динамически добавлять совершенно новые измерения (эффективно увеличивая существующий куб с дополнительными размерами и клонируя или изменяя исходные данные с помощью yourcube $dims[$...] ), но обнаружил, что поведение является немного непоследовательно. Наверное, лучше всего этого избежать, и сначала сконструируйте свой куб, прежде чем манипулировать им. Будут держать вас в курсе, если я где-нибудь.

Настойчивость

Очевидно, что одной из основных проблем, связанных с доступом к многопроцессорной базе данных, является возможность случайного вмешательства в нее с помощью несвоевременного нажатия клавиши. Поэтому я предполагаю, что это происходит очень рано и часто:

tempfilename<-gsub("[ :-]","",paste0("DBX",(Sys.time()),".cub"))
# save:
save(arr.cube,file=tempfilename)
# load:
load(file=tempfilename)

Надеюсь, это поможет!

Вопрос

Каков правильный способ структурирования многомерных данных с категориальными метками, накопленными в ходе повторных испытаний для исследовательского анализа в R? Я не хочу возвращаться к MATLAB.

объяснение

Я люблю функцию анализа АиРа и синтаксис (и ошеломляющие графики) гораздо лучше, чем MATLAB, и упорно работал, чтобы реорганизовать свой материал снова. Тем не менее, я постоянно зацикливаюсь на том, как организованы данные в моей работе.

MATLAB

Для меня типично работать с многомерными временными рядами, повторяющимися во многих испытаниях, которые хранятся в большом матричном ранге-3 тензоре многомерного массива SERIESxSAMPLESxTRIALS. Это время от времени поддается какой-то хорошей линейной алгебре, но неуклюже, когда дело касается другой переменной, а именно CLASS. Обычно метки классов хранятся в другом векторе размера 1x TRIALS .

Когда дело доходит до анализа, я в основном задумываюсь как можно меньше, потому что требуется очень много работы, чтобы собрать действительно хороший сюжет, который учит вас многому о данных в MATLAB. ( Я не единственный, кто так чувствует себя ).

р

В R я придерживался как можно ближе к структуре MATLAB, но все становится раздражающе сложным, пытаясь сохранить разметку класса отдельно; Мне пришлось бы продолжать передавать метки в функции, хотя я использую только их атрибуты. Итак, я сделал отдельный массив в списке массивов с помощью CLASS. Это добавляет сложности ко всем моим функциям apply() , но, по-видимому, стоит того, чтобы поддерживать согласованность (и ошибки).

С другой стороны, R просто не кажется дружелюбным с тензорами / многомерными массивами. Чтобы работать с ними, вам нужно захватить библиотеку abind . Документация по многомерному анализу, как и в этом примере, кажется, работает в предположении, что у вас есть огромная 2-D таблица данных, например, какая-то длинная средневековая прокрутка фрейма данных, и не упоминает, как получить «туда», откуда я ,

Как только я добираюсь до построения и классификации обработанных данных, это не такая большая проблема, так как к тому времени я проделал свой путь к структурам, дружественным к фреймворкам, с такими формами, как TRIALSxFEATURES ( melt очень помог с этим). С другой стороны, если я хочу быстро создать матрицу рассеянного экрана или гистограмму решетки для исследовательской фазы (т.е. статистические моменты, разделение, дисперсия между классами, гистограммы и т. Д.), Я должен остановиться и выяснить, как Я собираюсь apply() эти огромные многомерные массивы к чему-то, что понимают эти библиотеки.

Если я продолжаю стучать в джунглях, придумывая специальные решения для этого, я либо не буду улучшаться, либо у меня появятся мои собственные странные волшебные способы сделать это, что не имеет смысла никому ,

Итак, каков правильный способ структурирования многомерных данных с категориальными метками, накопленными в ходе повторных испытаний для исследовательского анализа в R? Пожалуйста, я не хочу возвращаться в MATLAB.

Бонус: я склонен повторять эти анализы по идентичным структурам данных для нескольких субъектов. Есть ли лучший общий способ, чем обертывание фрагментов кода в циклы?


Как уже отмечалось, многие из более мощных инструментов анализа и визуализации полагаются на данные в длинном формате. Разумеется, для преобразований, которые извлекают выгоду из матричной алгебры, вы должны хранить материал в массивах, но как только вы хотите провести параллельный анализ подмножеств ваших данных или засекретить материал по вашим данным, вы действительно хотите melt .

Вот пример, чтобы начать работу с data.table и ggplot .

Массив -> Таблица данных

Сначала давайте сделаем некоторые данные в вашем формате:

series <- 3
samples <- 2
trials <- 4

trial.labs <- paste("tr", seq(len=trials))
trial.class <- sample(c("A", "B"), trials, rep=T)

arr <- array(
  runif(series * samples * trials), 
  dim=c(series, samples, trials),
  dimnames=list(
    ser=paste("ser", seq(len=series)), 
    smp=paste("smp", seq(len=samples)), 
    tr=trial.labs
  )
)
# , , tr = Trial 1
#        smp
# ser         smp 1     smp 2
#   ser 1 0.9648542 0.4134501
#   ser 2 0.7285704 0.1393077
#   ser 3 0.3142587 0.1012979
#
# ... omitted 2 trials ...
# 
# , , tr = Trial 4
#        smp
# ser         smp 1     smp 2
#   ser 1 0.5867905 0.5160964
#   ser 2 0.2432201 0.7702306
#   ser 3 0.2671743 0.8568685

Теперь у нас есть трехмерный массив. Давайте melt и превратим его в data.table (примечание melt работает на data.frames , которые в основном являются data.table s sans bells & data.table , поэтому мы должны сначала расплавиться, а затем преобразовать в data.table ):

library(reshape2)
library(data.table)

dt.raw <- data.table(melt(arr), key="tr")  # we'll get to what the `key` arg is doing later
#       ser   smp   tr      value
#  1: ser 1 smp 1 tr 1 0.53178276
#  2: ser 2 smp 1 tr 1 0.28574271
#  3: ser 3 smp 1 tr 1 0.62991366
#  4: ser 1 smp 2 tr 1 0.31073376
#  5: ser 2 smp 2 tr 1 0.36098971
# ---                            
# 20: ser 2 smp 1 tr 4 0.38049334
# 21: ser 3 smp 1 tr 4 0.14170226
# 22: ser 1 smp 2 tr 4 0.63719962
# 23: ser 2 smp 2 tr 4 0.07100314
# 24: ser 3 smp 2 tr 4 0.11864134

Обратите внимание, насколько это было легко, при этом все наши измерительные метки просачивались в длинный формат. Одним из наворотов данных data.tables является возможность делать индексированные слияния между data.table s (так же, как MySQL индексированные соединения). Итак, мы сделаем это, чтобы привязать class к нашим данным:

dt <- dt.raw[J(trial.labs, class=trial.class)]  # on the fly mapping of trials to class
#          tr   ser   smp     value class
#  1: Trial 1 ser 1 smp 1 0.9648542     A
#  2: Trial 1 ser 2 smp 1 0.7285704     A
#  3: Trial 1 ser 3 smp 1 0.3142587     A
#  4: Trial 1 ser 1 smp 2 0.4134501     A
#  5: Trial 1 ser 2 smp 2 0.1393077     A
# ---                                    
# 20: Trial 4 ser 2 smp 1 0.2432201     A
# 21: Trial 4 ser 3 smp 1 0.2671743     A
# 22: Trial 4 ser 1 smp 2 0.5160964     A
# 23: Trial 4 ser 2 smp 2 0.7702306     A
# 24: Trial 4 ser 3 smp 2 0.8568685     A

Несколько вещей, чтобы понять:

  1. J создает data.table из векторов
  2. пытаясь data.table строки одной data.table данных с другой таблицей данных (т. е. используя data.table в качестве первого аргумента после скобки в [.data.table ), приводит к тому, что data.table влево (в языке MySQL) внешняя таблица ( dt в этом случае) во внутреннюю таблицу (созданную на лету J ) в этом случае. Соединение выполняется в key столбце (-ах) внешней data.table , как вы могли заметить, мы определили ранее на этапе преобразования melt / data.table .

Вам нужно будет прочитать документацию, чтобы полностью понять, что происходит, но подумайте о J(trial.labs, class=trial.class) что фактически эквивалентно созданию data.table с data.table(trial.labs, class=trial.class) , кроме J работает только при использовании внутри [.data.table .

Итак, теперь, за один простой шаг, у нас есть данные класса, привязанные к значениям. Опять же, если вам нужна матричная алгебра, сначала оперируйте свой массив, а затем в двух или трех простых командах вернитесь к длинному формату. Как отмечалось в комментариях, вы, вероятно, не хотите идти вперед и назад от форматов long to array, если у вас нет действительно веских оснований для этого.

Когда вещи находятся в data.table , вы можете легко группировать / data.table ваши данные (аналогично концепции split-apply-comb style). Предположим, мы хотим получить сводную статистику для каждой комбинации sample :

dt[, as.list(summary(value)), by=list(class, smp)]

#    class   smp    Min. 1st Qu. Median   Mean 3rd Qu.   Max.
# 1:     A smp 1 0.08324  0.2537 0.3143 0.4708  0.7286 0.9649
# 2:     A smp 2 0.10130  0.1609 0.5161 0.4749  0.6894 0.8569
# 3:     B smp 1 0.14050  0.3089 0.4773 0.5049  0.6872 0.8970
# 4:     B smp 2 0.08294  0.1196 0.1562 0.3818  0.5313 0.9063

Здесь мы просто даем data.table выражение ( as.list(summary(value)) ) для оценки для каждого class , подмножества smp данных (как указано в выражении). Нам нужен as.list чтобы результаты были собраны с помощью data.table виде столбцов.

Вы могли бы так же легко вычислить моменты (например, list(mean(value), var(value), (value - mean(value))^3 ) для любой комбинации переменных класса / образца / пробной серии.

Если вы хотите сделать простые преобразования в данные, это очень просто с data.table :

dt[, value:=value * 10]  # modify in place with `:=`, very efficient
dt[1:2]                  # see, `value` now 10x    
#         tr   ser   smp    value class
# 1: Trial 1 ser 1 smp 1 9.648542     A
# 2: Trial 1 ser 2 smp 1 7.285704     A

Это трансформация на месте, поэтому копий памяти нет, что делает ее быстрой. Как правило, data.table пытается максимально эффективно использовать память и как таковой является одним из самых быстрых способов сделать этот тип анализа.

Построение длинного формата

ggplot является фантастическим для построения данных в длинном формате. Я не буду вдаваться в подробности того, что происходит, но, надеюсь, изображения дадут вам представление о том, что вы можете сделать

library(ggplot2)
ggplot(data=dt, aes(x=ser, y=smp, color=class, size=value)) + 
  geom_point() +
  facet_wrap( ~ tr)

ggplot(data=dt, aes(x=tr, y=value, fill=class)) + 
  geom_bar(stat="identity") +
  facet_grid(smp ~ ser)

ggplot(data=dt, aes(x=tr, y=paste(ser, smp))) + 
  geom_tile(aes(fill=value)) + 
  geom_point(aes(shape=class), size=5) + 
  scale_fill_gradient2(low="yellow", high="blue", midpoint=median(dt$value))

Таблица данных -> Массив -> Таблица данных

Сначала нам нужно выполнить acast (из пакета reshape2 ) нашу таблицу данных обратно в массив:

arr.2 <- acast(dt, ser ~ smp ~ tr, value.var="value")
dimnames(arr.2) <- dimnames(arr)  # unfortunately `acast` doesn't preserve dimnames properly
# , , tr = Trial 1
#        smp
# ser        smp 1    smp 2
#   ser 1 9.648542 4.134501
#   ser 2 7.285704 1.393077
#   ser 3 3.142587 1.012979
# ... omitted 3 trials ...

На данный момент arr.2 выглядит так же, как и arr , за исключением значений, умноженных на 10. Обратите внимание, что нам пришлось отказаться от столбца class . Теперь давайте сделаем некоторую тривиальную матричную алгебру

shuff.mat <- matrix(c(0, 1, 1, 0), nrow=2) # re-order columns
for(i in 1:dim(arr.2)[3]) arr.2[, , i] <- arr.2[, , i] %*% shuff.mat

Теперь вернемся к длинному формату с melt . Обратите внимание на key аргумент:

dt.2 <- data.table(melt(arr.2, value.name="new.value"), key=c("tr", "ser", "smp"))

Наконец, давайте присоединимся к dt и dt.2 . Здесь вам нужно быть осторожным. Поведение data.table состоит в том, что внутренняя таблица будет соединена с внешней таблицей на основе всех ключей внутренней таблицы, если внешняя таблица не имеет ключей. Если внутренняя таблица имеет ключи, data.table присоединяется к ключу. Это проблема здесь, потому что наша предполагаемая внешняя таблица dt уже имеет ключ только tr от ранее, поэтому наше соединение будет происходить только в этом столбце. Из-за этого нам нужно либо сбросить ключ во внешней таблице, либо сбросить ключ (мы выбрали последний здесь):

setkey(dt, tr, ser, smp)
dt[dt.2]
#          tr   ser   smp    value class new.value
#  1: Trial 1 ser 1 smp 1 9.648542     A  4.134501
#  2: Trial 1 ser 1 smp 2 4.134501     A  9.648542
#  3: Trial 1 ser 2 smp 1 7.285704     A  1.393077
#  4: Trial 1 ser 2 smp 2 1.393077     A  7.285704
#  5: Trial 1 ser 3 smp 1 3.142587     A  1.012979
# ---                                             
# 20: Trial 4 ser 1 smp 2 5.160964     A  5.867905
# 21: Trial 4 ser 2 smp 1 2.432201     A  7.702306
# 22: Trial 4 ser 2 smp 2 7.702306     A  2.432201
# 23: Trial 4 ser 3 smp 1 2.671743     A  8.568685
# 24: Trial 4 ser 3 smp 2 8.568685     A  2.671743

Обратите внимание, что data.table выполняет объединения, сопоставляя ключевые столбцы , то есть - сопоставляя первый столбец внешней таблицы с первым столбцом / ключом внутренней таблицы, вторым со вторым и так далее, не считая столбца имена (здесь есть FR). Если ваши таблицы / ключи находятся не в том же порядке (как это было здесь, если вы заметили), вам нужно либо переупорядочить ваши столбцы, либо убедиться, что обе таблицы имеют ключи в столбцах, которые вы хотите, в том же порядке ( что мы здесь сделали). Причина, по которой столбцы были не в правильном порядке, data.table с первым соединением, которое мы сделали, чтобы добавить класс, связанный с tr и заставил этот столбец стать первым в data.table .





methodology