WedX - журнал о программировании и компьютерных науках

Что я делаю неправильно с этим изменением формы от длинного к широкому?

проблема

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

фон

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

> this.data
              Time Trial Class Channel      Value
     1: -100.00000     1    -1      V1  0.4551513
     2:  -96.07843     2    -1      V1  0.8241555
     3:  -92.15686     3    -1      V1  0.7667328
     4:  -88.23529     4    -1      V1  0.7475106
     5:  -84.31373     5    -1      V1  0.9810273
    ---                                          
204796:  884.31373   196     1      V4 50.2642220
204797:  888.23529   197     1      V4 50.5747661
204798:  892.15686   198     1      V4 50.5749421
204799:  896.07843   199     1      V4 50.1988299
204800:  900.00000   200     1      V4 50.7756015

В частности, приведенные выше данные имеют столбец Time с 256 уникальными числами от 0 до 900, которые повторяются для каждого Channel, для каждого Trial. Точно так же каждый Channel является одним из V1,V2,V3,V4, повторяющихся для каждого Time образца, для каждого Trial. Другими словами, любая комбинация Time,Trial,Channel однозначно определяет Value. Для простоты, все Trial до 100 имеют Class -1, а все выше 99 имеют Class 1. (Для тестирования, все Value в Class 1 имеют среднее значение 50, а Class 0 имеют среднее значение 0 . (Эти данные можно сгенерировать и настроить с помощью функции dummy.plug(), включенной в суть, которую я составил. )

Чтобы обрабатывать данные с использованием различных алгоритмов классификации машинного обучения, кажется, необходимо преобразовать данные во что-то более широкое, чтобы каждый из временных рядов имел свой собственный столбец, а другие оставались в виде идентификаторов. (Например, ступенчатому классификатору stepclass из klaR нужны функции в разных столбцах, поэтому он может выбирать, какие из них отбрасывать или добавлять в свою модель по мере обучения.) Поскольку есть повторные испытания, мне не удалось создать существующие функции, такие как работа семьи cast, поэтому я написал свою собственную:

##### converting from long table form to channel-split wide form #####
# for multivariate repeated time series
channel.form <- function(input.table,
                         value.col = "Voltage",
                         split.col = "Channel",
                         class.col = "Class",
                         time.col = "Time",
                         trial.col = "Trial") {
# Converts long table format to slightly wider format split by channels.
# For epoched datasets.

  setkeyv(input.table, class.col)

  chan.split <- split(input.table,input.table[,get(split.col)])

  chan.d <- cbind(lapply(chan.split, function(x){
    x[,value.col,with=FALSE]}))

  chan.d <- as.data.table(matrix(unlist(chan.d),
                            ncol = input.table[,length(unique(get(split.col)))], 
                            byrow=TRUE))

  # reintroduce class labels
  # since the split is over identical sections for each channel, we can just use
  # the first split's labels
  chan.d <- chan.d[,c(class.col):= chan.split[[1]][,get(class.col)]]
  chan.d[,c(class.col):=as.factor(get(class.col))]

  # similarly with time and trial labels
  chan.d <- chan.d[,Time:= chan.split[[1]][,get(time.col)]]
  chan.d <- chan.d[,Trial:= chan.split[[1]][,get(trial.col)]]

  return(chan.d) 
}

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

> this.data.training.channel
              V1        V2        V3        V4 Class       Time Trial
    1: -50.58389 -50.56397 -50.74251 -50.86700    -1 -100.00000     1
    2: -50.92713 -50.28009 -50.15078 -50.70161    -1  -96.07843     2
    3: -50.84276 -50.02456 -50.20015 -50.45228    -1  -76.47059     7
    4: -50.68679 -50.05475 -50.04270 -50.83900    -1  -72.54902     8
    5: -50.55954 -50.88998 -50.01273 -50.86856    -1  -68.62745     9
   ---                                                               
35836:  49.52361  49.37465  49.73997  49.10543     1  876.47059   194
35837:  49.93162  49.38352  49.62406  49.16854     1  888.23529   197
35838:  49.67510  49.63853  49.54259  49.81198     1  892.15686   198
35839:  49.26295  49.98449  49.60437  49.03918     1  896.07843   199
35840:  49.05030  49.42035  49.48546  49.73438     1  900.00000   200

На этом этапе я беру расширенную таблицу и передаю ее классификатору, такому как lda(), а затем проверяю ее на отдельной случайной части тех же данных:

lda.model <- lda(Class ~ . -Trial, this.data.training.channel)
lda.pred <- predict(lda.model, this.data.testing.channel)

симптомы

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

огромно разделенные данные двух классов в четырех каналах

> table(predicted = lda.pred$class, data = this.data.testing.channel[,Class])
         data
predicted   -1    1
       -1 2119 1878
       1  5817 5546

> 1-sum(lda.pred$class != this.data.testing.channel[,Class])/length(lda.pred$class)
[1] 0.4984375

> table(predicted = sda.pred$class, data = this.data.testing.channel[,Class])
         data
predicted   -1    1
       -1 3705 3969
       1  3719 3967

> 1-sum(sda.pred$class != this.data.testing.channel[,Class])/length(sda.pred$class)
[1] 0.4994792

Частота ошибок — это, по сути, подбрасывание монеты, несмотря на то, что значения из класса 1 примерно в 50 раз превышают значения из класса -1. Должно быть, я совершаю какую-то огромную ошибку (я думаю, что это ошибка программирования, иначе я бы закончил перекрестную проверку), но я потратил дни, подталкивая ее и переписывая код без каких-либо улучшений. (В качестве примера обратите внимание, что я получаю один и тот же результат независимо от того, масштабирую ли я входные значения так, чтобы они имели среднее значение 0, дисперсию 1.)

воспроизведение проблемы

Полный список, который можно запустить для воспроизведения проблемы, доступен здесь.

возможные проблемы я рассмотрел, что я пробовал

(полный список см. в предыдущих редакциях вопроса из-за соображений длины)

Я написал функцию (включенную в gist) для создания легко отделяемых фиктивных данных и написал еще одну функция для усреднения каждого из двух классов, ограненных Channel и окрашенных Class, как на графике выше. Игра с каждым из параметров (разница в средних значениях населения, количество каналов и т. д.), кажется, дает ожидаемый результат, а также просмотр соответствующих подмножеств с использованием вызовов типа this.data[Trial==1,unique(Time),by=Subject].

что мне нужно, чтобы решить это?

Буду очень признателен за любые советы по исправлению этого. Я просто не вижу, что я делаю неправильно.

Если бы кто-то либо диагностировал/обнаружил проблему, либо смог проиллюстрировать, используя другой подход, измененную таблицу из данных, которая работала с этими (популярными) функциями классификатора, я бы не просто согласился, я бы присудил награду (после тестирование, конечно).

информация о сеансе

R version 3.0.2 (2013-09-25)
Platform: x86_64-pc-linux-gnu (64-bit)

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
 [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] parallel  grid      stats     graphics  grDevices utils     datasets  methods  
[9] base     

other attached packages:
 [1] doMC_1.3.2              iterators_1.0.6         AUC_0.3.0              
 [4] LiblineaR_1.80-7        RcppRoll_0.1.0          RcppArmadillo_0.4.300.0
 [7] Rcpp_0.11.1             foreach_1.4.1           cvTools_0.3.2          
[10] robustbase_0.90-2       latticist_0.9-44        vcd_1.3-1              
[13] latticeExtra_0.6-26     lattice_0.20-29         pheatmap_0.7.7         
[16] RColorBrewer_1.0-5      klaR_0.6-10             MASS_7.3-29            
[19] ggplot2_0.9.3.1         reshape2_1.2.2          data.table_1.9.2       
[22] sda_1.3.3               fdrtool_1.2.12          corpcor_1.6.6          
[25] entropy_1.2.0           zoo_1.7-11              testthat_0.8           

loaded via a namespace (and not attached):
 [1] codetools_0.2-8  colorspace_1.2-4 combinat_0.0-8   compiler_3.0.2   DEoptimR_1.0-1  
 [6] dichromat_2.0-0  digest_0.6.4     gtable_0.1.2     gWidgets_0.0-52  labeling_0.2    
[11] munsell_0.4.2    plyr_1.8         proto_0.3-10     scales_0.2.3     stringr_0.6.2   
[16] tools_3.0.2   

  • Довольно неясно, как вы назначаете один и тот же класс, время, пробную версию для всех ваших разных значений канала. Я настоятельно рекомендую, если вы не можете, как вы написали, заставить cast делать то, что вы хотите, научиться правильно использовать cast или исследовать общие функции подмножества (или aggregate -типа). Если бы вы могли опубликовать небольшой воспроизводимый пример, чтобы обосновать присвоение данных всем четырем значениям Channel, а также подтвердить ваши утверждения о неудачной классификации, это тоже помогло бы. 19.05.2014
  • @CarlWitthoft Честно говоря, я беспокоился о том, чтобы спрятать вопрос в коде, поэтому я оставил большую часть его по существу (включая умеренное количество комментариев в коде). Я не верю, что cast можно использовать для решения этой проблемы самостоятельно (потратив на это несколько дней), потому что комбинация канала/пробной версии/времени однозначно определяет точку данных, что приводит к NA везде с редко размещенными значениями ( по крайней мере, однако я пробовал это с cast). Я тоже не собираю (думаю). Не могли бы вы подробнее объяснить, что вы подразумеваете под обоснованием присвоения данных всем четырем значениям Channel? 19.05.2014
  • Чего я не могу понять из образца вашего исходного набора данных, так это того, повторяются ли ваши переменные Time trial и value и как часто они повторяются, и как вы собираетесь реорганизовать или перегруппировать данные. 19.05.2014
  • Понятно. Я отредактировал, чтобы объяснить это дальше. Близкие избиратели, у меня практически неограниченная готовность ответить на этот вопрос, так что будьте конструктивны и скажите мне, что вам нужно ;) 19.05.2014
  • Я восхищаюсь вашим терпением при работе как с R, так и со Stackoverflow... совет: постарайтесь сделать свой вопрос более точным и конкретным. Если у вас несколько вопросов, разделите их на разные вопросы. В своем коде идите от простого к сложному. И самое главное, если вы рискуете умереть от передозировки R, помните, что есть Python и C++... 19.05.2014
  • Я очень ценю это! Я хотел бы могу сузить вопрос; это, вероятно, будет большой частью решения. 19.05.2014
  • сделайте свой вопрос самодостаточным и поместите его на одной странице - если вы не можете, это означает, что вы не потратили достаточно усилий и надеетесь, что кто-то другой сделает это за вас (подсказка: маловероятно, что это произойдет) 19.05.2014
  • Суть недостаточно самодостаточна? Конечно, есть немного кода, но большая его часть — функции для упрощения объяснения. Я, однако, усложню вопрос. 20.05.2014
  • Не могли бы вы также опубликовать свой sessionInfo()? 20.05.2014
  • Спасибо всем за хороший спорт и комментарии. Я думаю, что вернусь к этому вопросу, когда придет время убрать исправления, которые я вставил в свой собственный код, и убедиться, что это так же ясно. 20.05.2014

Ответы:


1

Я не смог воспроизвести вашу ошибку и обнаружил некоторые проблемы с dummy.plug(). Я сгенерировал данные с

library(data.table)
library(reshape2)
library("MASS")

set.seed(115)
pp<-dummy.plug(trial.count = 200,
    chan.count = 4,
    mean.diff = 100,
    value.name = "Value")

И меня не волнует data.table, поэтому я просто преобразовал его в базовый data.frame.

dd<-as.data.frame(pp)

Теперь вы говорите, что Time, Trial и Channel должны однозначно идентифицировать значение, но это не похоже на фиктивные данные. я вижу это

subset(dd, Time==-100 & Trial==1 & Channel=="V1")

#       Time Trial Class Channel      Value
# 1     -100     1    -1      V1 0.73642916
# 6401  -100     1    -1      V1 0.17648939
# 12801 -100     1    -1      V1 0.41366964
# 19201 -100     1    -1      V1 0.07044473
# 25601 -100     1    -1      V1 0.86583284
# 32001 -100     1    -1      V1 0.24255411
# 38401 -100     1    -1      V1 0.92473225
# 44801 -100     1    -1      V1 0.69989600

Таким образом, для каждой комбинации явно существует несколько значений. Итак, чтобы продолжить, я решил просто взять среднее значение наблюдаемых значений. У меня не было проблем с использованием dcast с

xx<-dcast(dd, Class+Time+Trial~Channel, fun.aggregate=mean)

Затем я разделяю наборы данных для обучения/тестирования.

train.trials = sample(unique(dd$Trial), 140)
train.data = subset(xx, Trial %in% train.trials)
test.data = subset(xx, !Trial %in% train.trials)

Затем я запустил lda, как указано выше.

lda.model <- lda(Class ~ . -Trial, train.data)
lda.pred <- predict(lda.model, test.data)

И я проверил, как я сделал

table(lda.pred$class, test.data$Class)
#        -1    1
#   -1  704    0
#   1     0 1216

И мне кажется, что я справляюсь намного лучше, чем ты.

Если что-то плохое не произошло, когда я преобразовал data.table в data.frame, похоже, есть проблемы с вашими тестовыми данными. Возможно, есть проблема с вашей функцией изменения формы без приведения. Увидев, что dcast работает просто отлично, возможно, вы захотите проверить, работает ли и ваша функция.

20.05.2014
  • Спасибо за подробный ответ; Я попробую прямо сейчас. 20.05.2014
  • Это оказалось и ответом на вопрос и тем ответом, который мне был нужен! Спасибо! 20.05.2014

  • 2

    MrFlick был прав по обоим пунктам. Для полноты картины вот ответ data.table с некоторыми дополнительными пояснениями.

    плохая фиктивная функция данных

    Фиктивная функция в приведенном выше описании была действительно плохой; ключевые строки таковы:

      dummy.data <- data.table(matrix(runif(length(time.vector)*trial.count*chan.count),
                                      ncol=chan.count),
                               Time=rep(time.vector,times = trial.count))
      setkey(dummy.data,Time)
      dummy.data <- dummy.data[,Trial:=seq_len(trial.count)]
    

    Поскольку Trial будет повторно использоваться в таблице после установки, все остальные столбцы должны соответствовать одной и той же перестановке (обтекание значений Trial). Быстрый способ сделать это — сортировка по Time, что является одним из эффектов setkey(). Как только это будет сделано, данные действительно могут быть однозначно отсортированы:

    # load dummy data
    set.seed(115)
    this.data <- dummy.plug(trial.count = 200,
                           chan.count = 4,
                           mean.diff = 50,
                           value.name = "Value")
    > this.data[(Trial==1 & Channel=="V1" & Time == -100),]
       Time Trial Class Channel     Value
    1: -100     1    -1      V1 0.7364292
    

    dcast работает сейчас

    Теперь, когда критерий уникальности выполнен, dcast работает с таблицей данных:

    > this.data.channel <- dcast.data.table(this.data,
    +                                                Class+Time+Trial~Channel,
    +                                                fun.aggregate=identity)
    Using 'Value' as value column. Use 'value.var' to override
    > this.data.channel
           Class Time Trial           V1         V2         V3          V4
        1:    -1 -100     1 7.364292e-01  0.8889176  0.4638730  0.61258621
        2:    -1 -100     2 9.030099e-02  0.1435559  0.1596734  0.88577669
        3:    -1 -100     3 6.685920e-01  0.1013146  0.7156151  0.51144831
        4:    -1 -100     4 9.154142e-04  0.2429634  0.3169072  0.05810808
        5:    -1 -100     5 7.383397e-01  0.3668977  0.3779892  0.34938949
       ---                                                                
    51196:     1  900   196 5.028103e+01 50.2810276 50.2810276 50.28102761
    51197:     1  900   197 5.080229e+01 50.8022872 50.8022872 50.80228716
    51198:     1  900   198 5.084255e+01 50.8425466 50.8425466 50.84254662
    51199:     1  900   199 5.096859e+01 50.9685913 50.9685913 50.96859133
    51200:     1  900   200 5.034459e+01 50.3445878 50.3445878 50.34458784
    

    Вы можете быстро проверить, правильно ли это работает:

    > this.data.channel[,unique(Trial),by=Class]
         Class  V1
      1:    -1   1
      2:    -1   2
      3:    -1   3
      4:    -1   4
      5:    -1   5
     ---          
    196:     1 196
    197:     1 197
    198:     1 198
    199:     1 199
    200:     1 200
    

    проверка классификации

    Остальная часть сути работает, как и фрагмент MrFlick.

    > lda.model <- lda(Class ~ . -Trial, this.data.training.channel)
    > lda.pred <- predict(lda.model, this.data.testing.channel)
    > table(predicted = lda.pred$class, data = this.data.testing.channel[,Class])
             data
    predicted   -1    1
           -1 5888    0
           1     0 9472
    > 1-sum(lda.pred$class != this.data.testing.channel[,Class])/length(lda.pred$class)
    [1] 1
    

    Почему я не мог заставить dcast работать раньше, мне придется покопаться в старой версии, чтобы посмотреть. Я подозреваю, что проблема перестановки (во время импорта вместо генерации), как указано выше, способствовала этому.

    20.05.2014
  • +1 за полноту. Определенно вздохнул с облегчением, узнав, что это не ошибка с dcast.data.table ;). 20.05.2014
  • Я определенно боюсь заглянуть под капот этого. Мне снятся кошмары о проблемах перестановки, как есть. 20.05.2014
  • Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    Для любых предложений по сайту: wedx@cp9.ru