如何在R中计算每个组的初始值差异?

7

I have data arranged like this in R:

indv    time    val
A          6    5
A         10    10
A         12    7
B          8    4
B         10    3
B         15    9

对于每个时间点的个体(indv),我想要计算从初始时间开始的值(val)变化。因此,我最终会得到类似这样的结果:

indv time   val val_1   val_change
A       6     5    5       0
A      10    10    5       5
A      12     7    5       2
B       8     4    4       0
B      10     3    4      -1
B      15     9    4       5

能否有人告诉我如何实现这个?我可以使用。
ddply(df, .(indv), function(x)x[which.min(x$time), ])

为了获得像这样的表格:
indv    time    val
A          6    5   
B          8    4   

然而,我无法找出如何创建一个名为val_1的列,使每个个体的最小值能够匹配。但是,如果我能做到这一点,我应该能够使用类似以下代码添加val_change列:

df['val_change'] = df['val_1'] - df['val']

编辑:下面发布了两种优秀的方法,但两种方法都依赖于我的时间列已经排序,以便小的时间值在高的时间值之上。我不确定我的数据总是这种情况。(我知道我可以先在Excel中进行排序,但我想避免这样做)。如果表格出现这种情况,我该怎么办:

indv    time    value
A          10   10
A           6   5
A          12   7
B           8   4
B          10   3
B          15   9
3个回答

5
这里提供一个使用 data.table 的解决方案,可以通过引用在数据表内存中高效设置。通过设置关键变量,可以按照关键字进行排序。
library(data.table)
DT <- data.table(df)  
# set key to sort by indv then time
setkey(DT, indv, time)
DT[, c('val1','change') := list(val[1], val - val[1]),by = indv]
# And to show it works....
DT
##    indv time val val1 change
## 1:    A    6   5    5      0
## 2:    A   10  10    5      5
## 3:    A   12   7    5      2
## 4:    B    8   4    4      0
## 5:    B   10   3    4     -1
## 6:    B   15   9    4      5

4
你可以使用基本函数来完成此操作,根据你的数据。
df <- read.table(text = "indv    time    val
A   6   5
A   10  10
A   12  7
B   8   4
B   10  3
B   15  9", header = TRUE)

我们首先按照变量“indv”在数据框“df”上进行了分割,使用了函数split()
sdf <- split(df, df$indv)

接下来,我们将每个sdf组件转换,以一种类似于您建议的方式添加val_1val_change变量。
sdf <- lapply(sdf, function(x) transform(x, val_1 = val[1],
                                         val_change = val - val[1]))

最后,我们安排将个别组件按行绑定成一个单独的数据框。
df <- do.call(rbind, sdf)
df

这句话的意思是:“这将会得到:”
R> df
    indv time val val_1 val_change
A.1    A    6   5     5          0
A.2    A   10  10     5          5
A.3    A   12   7     5          2
B.4    B    8   4     4          0
B.5    B   10   3     4         -1
B.6    B   15   9     4          5

编辑

为了解决评论中提出的排序问题,修改 lapply() 的调用,在 transform() 之前包含一个排序步骤。例如:

sdf <- lapply(sdf, function(x) {
                     x <- x[order(x$time), ]
                     transform(x, val_1 = val[1],
                               val_change = val - val[1])
                   })

我们使用中有:
## scramble `df`
df <- df[sample(nrow(df)), ]
## split
sdf <- split(df, df$indv)
## apply sort and transform
sdf <- lapply(sdf, function(x) {
                     x <- x[order(x$time), ]
                     transform(x, val_1 = val[1],
                               val_change = val - val[1])
                   })
## combine
df <- do.call(rbind, sdf)

这句话的英译中是:“再次产生:”。
R> df
    indv time val val_1 val_change
A.1    A    6   5     5          0
A.2    A   10  10     5          5
A.3    A   12   7     5          2
B.4    B    8   4     4          0
B.5    B   10   3     4         -1
B.6    B   15   9     4          5

亲爱的Gavin,感谢您的帮助!该方法确实适用于我的数据,但仅当时间列排序后,早期值出现在后期值之前时才有效。我不确定我的数据是否总是按那种方式排序。如果时间顺序混乱,例如下表中:indv time value A 10 10 A 6 5 A 12 7 B 8 4 B 10 3 B 15 9,是否有类似的方法? - Thomas
然后先按val排序或按time排序,无论你想要哪一种。先对数据框进行排序比在差分操作中处理它要容易得多。 - Gavin Simpson
1
我已经提出了一个解决方案。将来,为了避免回答的不断更新等问题,最好完整地阐述问题。希望我对问题所做的编辑有所帮助? - Gavin Simpson
亲爱的加文,谢谢。是的,编辑后很有帮助。很抱歉我没有完整地表达问题,直到读到答案时才意识到我已经错误表述了。下次我会更小心的! - Thomas
如何在lapply函数中执行以下操作,例如val_1+val_change。并将其赋值给变量new_val - dondapati
1
@user7462639 请提出一个带有可重现示例的新问题。 - Gavin Simpson

4
这是一个使用 ddply 的 plyr 解决方案。
ddply(df, .(indv), transform, 
      val_1 = val[1],
      change = (val - val[1]))

  indv time val val_1 change
1    A    6   5     5      0
2    A   10  10     5      5
3    A   12   7     5      2
4    B    8   4     4      0
5    B   10   3     4     -1
6    B   15   9     4      5

要获取您的第二个表格,请尝试以下操作:
ddply(df, .(indv), function(x) x[which.min(x$time), ])
  indv time val
1    A    6   5
2    B    8   4

编辑1

为了处理未排序的数据,就像你在编辑中发布的那样,请尝试以下操作:

unsort <- read.table(text="indv    time    value
A          10   10
A           6   5
A          12   7
B           8   4
B          10   3
B          15   9", header=T)


do.call(rbind, lapply(split(unsort, unsort$indv), 
                  function(x) x[order(x$time), ]))
    indv time value
A.2    A    6     5
A.1    A   10    10
A.3    A   12     7
B.4    B    8     4
B.5    B   10     3
B.6    B   15     9

现在,您可以将上述过程应用于已排序的数据框中

编辑2

更短的方法是使用doBy包中的sortBy函数对数据框进行排序

library(doBy)
orderBy(~ indv + time, unsort)
  indv time value
2    A    6     5
1    A   10    10
3    A   12     7
4    B    8     4
5    B   10     3
6    B   15     9

编辑3

您甚至可以使用ddply对您的数据框进行排序

ddply(unsort, .(indv, time), sort)
  value time indv
1     5    6    A
2    10   10    A
3     7   12    A
4     4    8    B
5     3   10    B
6     9   15    B

亲爱的Jilber,非常感谢您的帮助!但是,这只适用于“时间”列具有从低到高排序的值。我不确定在我的数据中是否总是这种情况,尽管我可以先在Excel中对其进行排序(但我正在尝试避免使用它)。当值未排序时,是否有一种方法,例如以下表格:indv时间价值A 10 10 A 6 5 A 12 7 B 8 4 B 10 3 B 15 9 - Thomas
亲爱的Jilber,你的编辑看起来非常好,非常感谢!我一整天都在尝试解决这个问题!现在我将使用我的真实数据进行测试,再次感谢您的帮助!!! - Thomas
很高兴能有所帮助。请查看我的第二次编辑,以了解更短的方法来对您的数据框进行排序。 - Jilber Urbina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接