写
在前面
本期大猫课堂将会开始一个新的系列:你不知道的R Tricks。这个系列将搬运stackoverflow.com(以后简称SO)上关于R数据处理的一些经典问答。大猫除了翻译原文,还会从初学者的角度为代码补充详细的解释。其实这些问题基本上都是大猫自己在数据处理过程中实际遇到的,看了SO上的答案不禁拍案叫绝,忍不住和大家分享。
第一期的主题是:如何巧为观测标记序号?这个问题在事件研究法时经常会遇到,但仅看标题小伙伴们是不是还觉得云里雾里?快点往下看吧!
提
出问题
话说有个小伙伴在StackOverflow上提出了这样一个问题:假设我现在有这样一个数据集:
建立样例数据集的代码:
▶ set.seed(42)
▶ dt <- data.table(group = sample(0:1, 10000, rep = TRUE), n = 0L)
其中,group的取值只能是0和1, 表示一件事情是否发生。你可以把group理解为一个人是否去健身房,如果连续出现1则意味着那几天每天都去,出现0则说明那人偷懒了。现在的问题是,我希望为每个连续的0或者1编号。例如连续出现3个0, 那么n就标记为1、2、3,如果后面接了一个1, 那么就重新从1开始标记。也即最终的结果应该是:
可以看到,group每变动一回,n就重新计数一次。那么这个代码究竟该怎么写呢?其实只需要一行就够啦!
原问题大家可以访问以下链接:http://stackoverflow.com/questions/25415749/creating-a-sequence-in-a-data-table-depending-on-a-column
解
决问题
在解决本问题的过程中我们需要用到data.table包!
”
虽然最终版本的代码只需要一行,但在这里大猫将会把它拆解为三部分:
首先,我们需要有一个变量能标记出group的变化。也就是当group不变时取0,变化时取1。在R中,求差分的函数diff非常适合完成这个任务。它可以计算当前观测和上一行观测相比变化了多少。我们试着用一下:
▶ dt[, diff := c(0, diff(group))]
结果如下:
看,现在每当group发生变化,diff就非零,但是diff只能在0、-1、1中变动,并不能把每次变化都用1、2、3……的数字给标记出来。那如何能够把每次变化都累加起来呢?这时就需要cumsum累加函数:
▶ dt[, cumsum := cumsum(abs(diff))]
结果如下:
看,diff每变化一次,cumsum就把这种变化累加起来了(注意我们用到了abs绝对值函数)。目前为止,我们已经成功把每次变化都分组并加以标号(见cumsum变量),看起来是不是几乎大功告成了?最后一步,我们只需要在每个by=cumsum组中将观测从1开始标号即可:
▶ dt[, n := seq(.N), by = cumsum]
最终结果为:
注意,我们这里用cumsum的值进行了分组,并且用了seq(.N)这个语句。".N"表示当前by组有多少的观测,而seq(.N)则产生从1至.N的一个整数序列。
例如,对于上面的第一行和第二行观测来说,他们同属于cumsum=0这组。因为这组一共只有两行,所以.N=2,而seq(.N)就产生{1, 2}这样一个整数序列,并最终赋值给n。
如果把上面三步写成一行代码就是这个样子:
▶ dt[, n := seq(.N), by = list(cumsum(c(0, abs(diff(group)))))]
本
期总结
本期大猫带领大家学习了一个为分组观测进行编号的小技巧。还记得在开篇大猫说这个技巧在事件研究法中特别实用吗?因为在事件研法中,我们一般会给事件日标为1,非事件日标为0,对于每个事件之间的一段时间,我们往往希望能够用1开始为其标号,这对于后续统计相当有用。此外,在做一些游程检验的过程中,这个技巧也非常管用。
我是大猫,咱们下期见!