R :: data.table: создать текущий баланс по группам, используя предыдущий баланс и построчную итерацию - PullRequest
6 голосов
/ 30 сентября 2019

У меня есть следующий DT (data.table) в R.

dt <- fread("
id| rowids | charge | payment | balance
a |   1    |  7.1   |   0     |     
a |   2    |  1.2   |   3     |   
a |   3    |  1.7   |   1     |   
b |   1    |  8.1   |   0     |   
b |   2    |  2.5   |   4     |   
b |   3    |  2.3   |   2     |   
b |   4    |  3.2   |   1     |   
            ", 
            sep = "|",
            colClasses = c("character", "numeric", "numeric", "numeric", 
"numeric"))

"Баланс" должен быть вычислен внутри каждой группы идентификаторов как "баланс <- предыдущий.row.balance + обвинение- оплата ", где" previous.row.balance "- предыдущая запись строки" balance ". </p>

Я изначально недооцениваю сложность вычисления баланса бега. Я думал о dt[,previous.row.balance := (shift(balance,1),by=id]. Но R делает векторизованные вычисления. У меня не было значений в «balance», доступных для выполнения shift (), так как «balance» будет вычисляться через итерацию строка за строкой.

Я искал в StackOverflow и нашел аналогичный вопроси его первый ответ очень помог мне продумать весь процесс. Я адаптировал код в первом ответе к своей проблеме и получил следующий код, работающий чудесно, чтобы сгенерировать текущий баланс по группам.

dt[rowids == 1, balance := charge, by=.(id)]
dt[rowids != 1, balance :=
    dt[,
        {
            balance1 <- balance[1L]
            .SD[rowids != 1,
                {balance1 <-  balance1 + charge - payment
                    .(balance1)
                },
                by=.(rowids)]
        },
        by=.(id)][, -1L:-2L]
]

Вот мои вопросы.

  1. Iдо сих пор не могу понять, как by=.(id)][, -1L:-2L], цепочка скобок отработала итерацию. Поскольку в коде не используется shift() by = group, я думаю, [, -1L:-2L] выполняет эту задачу для выполнения итерации. Но как? Что [, -1L:-2L] на самом деле делает здесь?

Извините, что я должен задать этот вопрос здесь, вместо того, чтобы комментировать или задавать под этот вопрос . Причина в том, что я новичок в StackOverflow только с 1 очком репутации. Мне не разрешено комментировать первоначальный ответ на этот вопрос. Я также хотел бы проголосовать за этот ответ. Прежде чем я смогу это сделать, я должен заработать больше очков.

Есть ли другой способ: использовать data.table и R векторизованное вычисление для достижения этой цели текущего баланса, не заключая ни одной петли для итерации строки?

Любое понимание или мысль приветствуются!

Ответы [ 2 ]

4 голосов
/ 30 сентября 2019

Относительно вашего вопроса # 2:

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

dt[, balance2 := cumsum(charge - payment), id]


dt
#    id rowids charge payment balance balance2
# 1:  a      1    7.1       0     7.1      7.1
# 2:  a      2    1.2       3     5.3      5.3
# 3:  a      3    1.7       1     6.0      6.0
# 4:  b      1    8.1       0     8.1      8.1
# 5:  b      2    2.5       4     6.6      6.6
# 6:  b      3    2.3       2     6.9      6.9
# 7:  b      4    3.2       1     9.1      9.1
3 голосов
/ 01 октября 2019

Поскольку @IceCreamToucan ответил на часть 2 (как улучшить код), я просто расскажу о части 1 (почему x[, -1:-2] работает). Из ?data.table мы знаем, что в общем случае поле j можно использовать для выбора столбцов :

Когда j является вектором имен столбцов или позиций ввыберите (как в data.frame) [, затем он ведет себя как с data.frame].

(Слова в скобках являются моей правкой для завершения предложения.)

В частности, когда j принимает форму n:m, ...

  • Если все n..m отрицательные или нулевые, то указанные столбцы удаляются
  • Если все n..m положительные или нулевые, то выбранные столбцы выбираются

Вы также можете увидеть это поведение с j, установленным на -c(1,2) или !c(1,2) или !(1:2) или -(1:2).

Это поведение основано на специальном разборе j, чтобы проверить, является ли : или ! или - функцией верхнего уровня.

Далее важнознать, что столбцы в by= помещаются как первые столбцы в таблице .

Комбинируя эти две точки в примере OP, вы получаете by=id в качестве первого столбца (внешний символ) и by=rowids в качестве второго столбца (внутренний элемент). После того, как они будут сброшены с помощью [, -1L:-2L], у вас останется выражение .(balance1).

...