【问题标题】:find First/ Last observation value by group?按组查找第一个/最后一个观察值?
【发布时间】:2016-07-09 05:00:46
【问题描述】:

我正在尝试按组查找第一个/最后一个观察结果。我厌倦了 R 和 excel(因为它在 R 中很慢所以我尝试了 excel)。 excel 用了不到 1 ,但 R 用了 8 分钟!!! >。两者的代码逻辑几乎相同。

数据是有关购买水果的面板数据。同一购物者可以在不同时间多次购买。 我有 233,000 次观察。 数据是这样的(按天购物者第一和天排序):

    Day Shopper Choice
    1   A   apple
    2   A   apple
    1   B   Banana
    1   C   apple
    2   C   Banana
    3   C   apple
    1   D   berry
    2   D   berry

我的第一次观察的 r 代码。我想通过指标“1”标记组中的第一个观察,一个新列。

 for (i in 1:n)
 { ifelse (dt$shopper[i+1]==dt$shopper[i],newcol[i+1]<-0,newcol[i+1]<-1)
  }

我的excel代码是: if(B2B1,1,0)

我需要给定相同的购物者回购矩阵。定义回购:“第一次购买”的回购是“第二次购买”;而“第2次购买”的回购是“第3次购买”最后一次购买没有回购。对不起,这听起来像一个扭曲。 所以我的解决方案是获得两个选择 col 并将第二个 col 移到上一行,这样我就可以通过购物者/或聚合计算回购矩阵。tagging 第一组 obs 的所需输出应如下所示。有了choice和choice 2的列,我可以通过nrow计算回购矩阵。

Day Shopper Choice  tagging choice 2
 1  A   apple      0       *apple*
 2  A   apple      *apple*  0
 1  B   Banana     0        0
 1  C   apple      0        Banana
 2  C   Banana     Banana   apple 
 3  C   apple      apple    0
 1  D   berry      0        *berry*
 2  D   berry      *berry*  0

[更新]。如果该用户只购买了一次,则不会再购买。如果购买是用户最后一次购买,则不进行回购。所以在这种情况下最终选择的回购矩阵是

        second  inside bracket are the probability  
first   apple banana berry   
apple   1 (0.5)  1      0
banana  1        0 (0)  0
berry   0        0      1 (1)

以下是我在标记用户首次购买后如何计算回购矩阵。 i 是行(苹果、香蕉、浆果等),j(苹果、香蕉、浆果等)是列。 [速度还可以,我在标记和添加辅助选择列后回购矩阵是40*40)

    for (i in 1:n){
    for(j in 1:n){
    repurchase_matrix[i,j]=nrow(dt[dt[,1]==i&dt[,2]==j,])}}

【问题讨论】:

  • 慢的不是 R。这是你的代码。 ifelse() 是矢量化的。另外,我认为您甚至不能在 ifelse() 是/否参数中使用 &lt;- 赋值。但更进一步,一旦你摆脱了for() 循环,ifelse() 也会相当慢。如果这是对大数据的分组操作,我会推荐 data.table 包以提高效率。
  • 1-(B2=B1) 应该比 if... 更快...仍然 Richard 是对的:您的代码执行方式需要很长时间...
  • @RichardScriven。谢谢!我是 R.:( 我也尝试过 (if, else),仍然很慢。有什么建议可以让它更快吗?
  • 您只想将每个组的第一行指示符设为 1,否则设为 0?请根据您的示例数据提供所需的输出。
  • @DirkReichel,谢谢。我对 excel 的速度非常满意,但还是谢谢你。我会试试的。即使我在 excel 中使用了 if,完成标记第一个/最后一个观察结果也需要不到一秒钟的时间。在这种情况下如何让我的 R 代码更快?

标签: r excel


【解决方案1】:

首先假设数据按Shopper排序,再按Day升序排序,可以添加一列表示购买编号

df$Purchase <- unlist(with(df, tapply(Shopper, Shopper, seq_along)))
df
#  Day Shopper Choice Purchase
#1   1       A  apple        1
#2   2       A  apple        2
#3   1       B Banana        1
#4   1       C  apple        1
#5   2       C Banana        2
#6   3       C  apple        3
#7   1       D  berry        1
#8   2       D  berry        2

然后将数据框重塑为“宽”格式

df.w <- reshape(df[c('Shopper', 'Choice', 'Purchase')],
                idvar='Shopper', v.names='Choice', timevar='Purchase',
                direction='wide')
df.w
#  Shopper Choice.1 Choice.2 Choice.3
#1       A    apple    apple     <NA>
#3       B   Banana     <NA>     <NA>
#4       C    apple   Banana    apple
#7       D    berry    berry     <NA>

最后你计算前两次购买的回购矩阵

with(df.w, prop.table(table(First=Choice.1, Second=Choice.2)))
#        Second
#First        apple    Banana     berry
#  apple  0.3333333 0.3333333 0.0000000
#  Banana 0.0000000 0.0000000 0.0000000
#  berry  0.0000000 0.0000000 0.3333333

要计算所有购买的回购矩阵,从每两次连续购买的回购矩阵开始

repurchase <- lapply(seq(2, ncol(df.w) - 1),
                     function(i) table(First=df.w[[i]], Second=df.w[[i + 1]]))
repurchase <- simplify2array(repurchase)
repurchase
#, , 1
#
#        Second
#First    apple Banana berry
#  apple      1      1     0
#  Banana     0      0     0
#  berry      0      0     1
#
#, , 2
#
#        Second
#First    apple Banana berry
#  apple      0      0     0
#  Banana     1      0     0
#  berry      0      0     0

然后将所有矩阵相加得到“总”回购矩阵

apply(repurchase, 1:2, sum)
#        Second
#First    apple Banana berry
#  apple      1      1     0
#  Banana     1      0     0
#  berry      0      0     1

(绝对频率)

prop.table(apply(repurchase, 1:2, sum))
#        Second
#First    apple Banana berry
#  apple   0.25   0.25  0.00
#  Banana  0.25   0.00  0.00
#  berry   0.00   0.00  0.25

(相对频率)

【讨论】:

  • 回购我的意思是:“第一次购买”的回购是第二次购买;而“第2次购买”的回购是“第3次购买”最后一次购买没有回购。抱歉,这听起来像是一个扭曲。
  • @Phdaml 我更新了答案(不确定您如何计算概率)。
  • @Phdaml 如果Choice 是一个因素,那么所有的回购子矩阵应该具有相同的维度。我认为问题可能在于您的数据框中 Choice 是一个字符向量,而不是一个因素。
  • 您是对的!您的代码产生的输出与我的原始代码相同,而且速度更快。如果你有时间,你能把它改成 factor, work 解释一下为什么吗?
  • @Phdaml 这是因为table() 的工作方式。当参数是一个因子时,table() 计算每个级别的出现次数(包括未使用的级别,其计数为零),因此返回的数组始终具有相同的维度。而如果参数是字符向量,则它永远不会报告零计数(因为它无法知道变量可以采用的所有可能值),因此返回的数组在每次迭代时的大小可能会有所不同。
【解决方案2】:

R,我们可以使用dplyr。按“Shopper”分组后,使用逻辑条件row_number() &lt; 2 创建第一次观察的“标志”列,并根据需要将逻辑转换为整数。

library(dplyr)
df1 %>% 
   group_by(Shopper) %>%
   mutate(Flag = as.integer(row_number() < 2))

如果我们可以使用最小和最大'Day'作为标识符,那么使用基于它的逻辑条件。

df1 %>% 
     group_by(Shopper) %>%
     mutate(Flag = as.integer(Day %in% range(Day)))

或使用data.table

library(data.table)
setDT(df1)[, Flag := as.integer(Day %in% range(Day)), by = Shopper]

或者使用base R,我们可以比较之前的'Shopper'和当前的'Shopper'(假设数据集已经订购)

i1 <- with(df1, Shopper[-1]!= Shopper[-nrow(df1)])
as.integer(c(TRUE, i1)|c(i1, TRUE))
#[1] 1 1 1 1 0 1 1 1

所有这些方法都应该比 OP 代码中的 for 循环更快。

更新

根据更新后的预期输出,如果我们需要将第一个观测值替换为“0”而其他观测值保持不变,则可以使用 ifelsereplace 并使用“标记”的 lead ,我们创建'tagChoice2'。

df1 %>%
   group_by(Shopper) %>% 
   mutate(tagging = ifelse(row_number()==1, "0", as.character(Choice)), 
          tagChoice2 = lead(tagging, default = "0"))   
#   Day Shopper Choice tagging tagChoice2
#  <int>   <chr>  <chr>   <chr>      <chr>
#1     1       A  apple       0      apple
#2     2       A  apple   apple          0
#3     1       B Banana       0          0
#4     1       C  apple       0     Banana
#5     2       C Banana  Banana      apple
#6     3       C  apple   apple          0
#7     1       D  berry       0      berry
#8     2       D  berry   berry          0

【讨论】:

  • @akrun 请添加data.table 解决方案好吗?
  • @UweBlock 补充说。
  • @Akrun 谢谢。我很好奇你为什么使用Day %in% range(Day) 而不是Day == min(Day)
  • @UweBlock range 给出了minmax。我以为 OP 想要标记他们两个
【解决方案3】:

我正在寻找通过分组查找列的第一个和最后一个值的答案 在data.table。到处看了看,想了想,就这样吧。

按组创建行顺序:

library(data.table)

DT <- data.table(col1 = rep(LETTERS[1:2], each = 4), col2 = c(3,12,5,56,6,678,233,70))
setorder(DT, col1, col2)
DT
   col1 col2
1:    A    3
2:    A    5
3:    A   12
4:    A   56
5:    B    6
6:    B   70
7:    B  233
8:    B  678

DT[, rank := order(col2), by = col1]
DT
   col1 col2 rank
1:    A    3    1
2:    A    5    2
3:    A   12    3
4:    A   56    4
5:    B    6    1
6:    B   70    2
7:    B  233    3
8:    B  678    4

按组创建第一个和最后一个值:

DT[, first_val := col2[1], by = col2]
DT[, last_val := col2[.N], by = col1]
DT
   col1 col2 rank first_val last_val
1:    A    3    1         3       56
2:    A    5    2         3       56
3:    A   12    3         3       56
4:    A   56    4         3       56
5:    B    6    1         6      678
6:    B   70    2         6      678
7:    B  233    3         6      678
8:    B  678    4         6      678

【讨论】:

    【解决方案4】:

    您可以尝试将 Microsoft R open 安装为默认 R。在数学计算方面,它比 R base 快得多。因为它使用了更多的核心,而 R.BASE 只使用一个核心来计算。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多