【问题标题】:Count cumulative unique factors separated by semicolon Grouped by Name计算由分号分隔的累积唯一因子按名称分组
【发布时间】:2016-04-02 19:47:03
【问题描述】:

这就是我的数据框的样子。最右边的两列是我想要的列。我正在计算每一行的唯一 FundTypes 的累积数量。第 4 列是所有“ActivityType”的累积唯一计数,第 5 列是仅“ActivityType=="Sale”的累积唯一计数。

dt <- read.table(text='

Name      ActivityType     FundType  UniqueFunds(AllTypes) UniqueFunds(SaleOnly)         

John       Email               a            1                     0
John       Sale                a;b          2                     2 
John       Webinar             c;d          4                     2
John       Sale                b            4                     2
John       Webinar             e            5                     2
John       Conference          b;d          5                     2
John       Sale                b;e          5                     3
Tom        Email               a            1                     0
Tom        Sale                a;b          2                     2 
Tom        Webinar             c;d          4                     2
Tom        Sale                b            4                     2
Tom        Webinar             e            5                     2
Tom        Conference          b;d          5                     2
Tom        Sale                b;e;f        6                     4                    

                         ', header=T, row.names = NULL)

我尝试过dt[, UniqueFunds := cumsum(!duplicated(FundType)&amp; !FundType=="") ,by = Name],但例如,它将 a & a;b & c;d 计为 3 个唯一值,而不是所需的 4 个唯一值,因为这些因素用分号分隔。请告诉我一个解决方案.

更新:我的真实数据集看起来更像这样:

dt <- read.table(text='

    Name      ActivityType     FundType  UniqueFunds(AllTypes) UniqueFunds(SaleOnly)         
    John       Email               ""           0                     0
    John       Conference          ""           0                     0
    John       Email               a            1                     0
    John       Sale                a;b          2                     2 
    John       Webinar             c;d          4                     2
    John       Sale                b            4                     2
    John       Webinar             e            5                     2
    John       Conference          b;d          5                     2
    John       Sale                b;e          5                     3
    John       Email               ""           5                     3
    John       Webinar             ""           5                     3
    Tom        Email               a            1                     0
    Tom        Sale                a;b          2                     2 
    Tom        Webinar             c;d          4                     2
    Tom        Sale                b            4                     2
    Tom        Webinar             e            5                     2
    Tom        Conference          b;d          5                     2
    Tom        Sale                b;e;f        6                     4                    

                             ', header=T, row.names = NULL)

唯一的累积向量需要考虑缺失值。

【问题讨论】:

  • FundCode 不是您的数据框的一部分,您能澄清一下吗?
  • 这是一个错误。谢谢你抓住它。我修好了。
  • 也许我遗漏了一些东西,但对于 Name == "John" &amp; ActivityType == "Sale",只有 3 个独特的 FundTypes - a;b(第 2 行),b(第 4 行) )和b;e(第7行),不是吗?除非您在这里指的是其他内容:“但它会将 a;b 和 b;c 和 c;d 视为 3 个唯一值”?
  • @nrussell 你是对的,我的示例超出了我提供的数据框的上下文。这更像是一个普遍的例子。如果您认为我在帖子中的内容令人困惑,我可以解决它。
  • 这可能有助于澄清这一点 - 或者可能只是通过将第 4 行从 b 更改为 b;c for FundType 来编辑示例数据,并包括用于生成第四行的代码和第五列。

标签: r data.table dplyr zoo


【解决方案1】:

nrussell 提出了一个编写自定义函数的简洁解决方案。让我放弃我得到的东西。正如您所尝试的那样,我尝试使用cumsum()duplicated()。我做了两个大手术。一个用于alltype,另一个用于saleonly。首先,我为每个名称创建了索引。然后,我拆分 FundType 并使用 splitstackshape 包中的 cSplit() 将数据格式化为长格式。然后,我为每个名称的每个索引号选择了最后一行。最后,我只选择了一栏,alltype

library(splitstackshape)
library(zoo)
library(data.table)

setDT(dt)[, ind := 1:.N, by = "Name"]
cSplit(dt, "FundType", sep = ";", direction = "long")[,
    alltype := cumsum(!duplicated(FundType)), by = "Name"][,
    .SD[.N], by = c("Name", "ind")][, list(alltype)] -> alltype

第二次手术仅供销售。基本上,我对出售的子集数据重复了相同的方法,即ana。我还创建了一个没有销售的数据集,即ana2。然后,我创建了一个包含两个数据集的列表(即l)并绑定它们。我用Nameind更改了数据集的顺序,为每个名称和索引号取最后一行,照顾NA(填充NA并将每个名称的第一个NA替换为0),最后选择了一个柱子。最后的操作是将原来的dtalltypesaleonly组合起来。

# data for sale only
cSplit(dt, "FundType", sep = ";", direction = "long")[
    ActivityType == "Sale"][,
    saleonly := cumsum(!duplicated(FundType)), by = "Name"] -> ana

# Data without sale
cSplit(dt, "FundType", sep = ";", direction = "long")[
    ActivityType != "Sale"] -> ana2 

# Combine ana and ana2
l <- list(ana, ana2)
rbindlist(l, use.names = TRUE, fill = TRUE) -> temp
setorder(temp, Name, ind)[,
    .SD[.N], by = c("Name", "ind")][,
    saleonly := na.locf(saleonly, na.rm = FALSE), by = "Name"][,
    saleonly := replace(saleonly, is.na(saleonly), 0)][, list(saleonly)] -> saleonly

cbind(dt, alltype, saleonly)

    Name ActivityType FundType UniqueFunds.AllTypes. UniqueFunds.SaleOnly. ind alltype saleonly
 1: John        Email        a                     1                     0   1       1        0
 2: John         Sale      a;b                     2                     2   2       2        2
 3: John      Webinar      c;d                     4                     2   3       4        2
 4: John         Sale        b                     4                     2   4       4        2
 5: John      Webinar        e                     5                     2   5       5        2
 6: John   Conference      b;d                     5                     2   6       5        2
 7: John         Sale      b;e                     5                     3   7       5        3
 8:  Tom        Email        a                     1                     0   1       1        0
 9:  Tom         Sale      a;b                     2                     2   2       2        2
10:  Tom      Webinar      c;d                     4                     2   3       4        2
11:  Tom         Sale        b                     4                     2   4       4        2
12:  Tom      Webinar        e                     5                     2   5       5        2
13:  Tom   Conference      b;d                     5                     2   6       5        2
14:  Tom         Sale    b;e;f                     6                     4   7       6        4

编辑

对于新的数据集,我尝试了以下方法。基本上,我将我的方法用于这个新数据集的 saleonly 数据。修订版仅在alltype 部分中。首先,我添加了索引,将“”替换为 NA,并使用具有非 NA 值的行对数据进行子集化。这是temp。其余的与上一个答案相同。现在我想在 FundType 中有 NA 的数据集,所以我使用了setdiff()。使用rbindlist(),我将这两个数据集组合并创建了temp。其余的与上一个答案相同。销售部分没有任何变化。我希望这对您的真实数据有用。

### all type

setDT(dt)[, ind := 1:.N, by = "Name"][,
    FundType := replace(FundType, which(FundType == ""), NA)][FundType != ""] -> temp
cSplit(temp, "FundType", sep = ";", direction = "long")[,
    alltype := cumsum(!duplicated(FundType)), by = "Name"] -> alltype


whatever <- list(setdiff(dt, temp), alltype)
rbindlist(whatever, use.names = TRUE, fill = TRUE) -> temp
setorder(temp, Name, ind)[,.SD[.N], by = c("Name", "ind")][,
    alltype := na.locf(alltype, na.rm = FALSE), by = "Name"][,
    alltype := replace(alltype, is.na(alltype), 0)][, list(alltype)] -> alltype


### sale only
cSplit(dt, "FundType", sep = ";", direction = "long")[
    ActivityType == "Sale"][,
    saleonly := cumsum(!duplicated(FundType)), by = "Name"] -> ana

cSplit(dt, "FundType", sep = ";", direction = "long")[
    ActivityType != "Sale"] -> ana2

l <- list(ana, ana2)
rbindlist(l, use.names = TRUE, fill = TRUE) -> temp
setorder(temp, Name, ind)[,
    .SD[.N], by = c("Name", "ind")][,
    saleonly := na.locf(saleonly, na.rm = FALSE), by = "Name"][,
    saleonly := replace(saleonly, is.na(saleonly), 0)][, list(saleonly)] -> saleonly

cbind(dt, alltype, saleonly)


    Name ActivityType FundType UniqueFunds.AllTypes. UniqueFunds.SaleOnly. ind alltype saleonly
 1: John        Email       NA                     0                     0   1       0        0
 2: John   Conference       NA                     0                     0   2       0        0
 3: John        Email        a                     1                     0   3       1        0
 4: John         Sale      a;b                     2                     2   4       2        2
 5: John      Webinar      c;d                     4                     2   5       4        2
 6: John         Sale        b                     4                     2   6       4        2
 7: John      Webinar        e                     5                     2   7       5        2
 8: John   Conference      b;d                     5                     2   8       5        2
 9: John         Sale      b;e                     5                     3   9       5        3
10: John        Email       NA                     5                     3  10       5        3
11: John      Webinar       NA                     5                     3  11       5        3
12:  Tom        Email        a                     1                     0   1       1        0
13:  Tom         Sale      a;b                     2                     2   2       2        2
14:  Tom      Webinar      c;d                     4                     2   3       4        2
15:  Tom         Sale        b                     4                     2   4       4        2
16:  Tom      Webinar        e                     5                     2   5       5        2
17:  Tom   Conference      b;d                     5                     2   6       5        2
18:  Tom         Sale    b;e;f                     6                     4   7       6        4

【讨论】:

  • 非常感谢您的回答!
  • @gibbz00 很高兴为您提供帮助。 :)
  • 我的原始数据框是 3M+ 行。 “alltype”和“saleonly”大约有 100 万行。不确定最终的 cbind 将如何工作。原始数据框中的 FundType 列有很多缺失值。例如,alltype 以 1122233 等开头,而不是 00001122333。如何修改您的解决方案以考虑所有缺失值?
  • @gibbz00 你能创建一个小例子来捕捉你的真实数据的性质吗?然后,SO 用户可以提供更好的解决方案。如果您可以添加另一个数据集作为编辑部分,那就太好了。
  • 我刚刚编辑了它。我希望它能更好地代表问题。
【解决方案2】:

我认为这是实现您所追求的目标的一种方式。首先添加一个辅助索引变量来维护输入顺序;和keying Name

Dt <- copy(dt[, 1:3, with = FALSE])[, gIdx := 1:.N, by = "Name"]
setkeyv(Dt, "Name") 

为了清楚起见,我使用了这个函数

n_usplit <- function(x, spl = ";") length(unique(unlist(strsplit(x, split = spl)))) 

而不是即时输入正文的表达式 - 下面的操作足够密集,因为它没有一堆嵌套函数调用令人费解的事情。

最后,

Dt[Dt, allow.cartesian = TRUE][
  gIdx <= i.gIdx, 
  .("UniqueFunds(AllTypes)" = n_usplit(FundType),
    "UniqueFunds(SaleOnly)" = n_usplit(FundType[ActivityType == "Sale"])),
  keyby = "Name,i.gIdx,i.ActivityType,i.FundType"][,-2, with = FALSE]
#      Name i.ActivityType i.FundType UniqueFunds(AllTypes) UniqueFunds(SaleOnly)
# 1:   John          Email          a                     1                     0
# 2:   John           Sale        a;b                     2                     2
# 3:   John        Webinar        c;d                     4                     2
# 4:   John           Sale          b                     4                     2
# 5:   John        Webinar          e                     5                     2
# 6:   John     Conference        b;d                     5                     2
# 7:   John           Sale        b;e                     5                     3
# 8:    Tom          Email          a                     1                     0
# 9:    Tom           Sale        a;b                     2                     2
# 10:   Tom        Webinar        c;d                     4                     2
# 11:   Tom           Sale          b                     4                     2
# 12:   Tom        Webinar          e                     5                     2
# 13:   Tom     Conference        b;d                     5                     2
# 14:   Tom           Sale      b;e;f                     6                     4

我觉得我可以用 SQL 更轻松地解释这一点,但我们开始吧:

  1. 自行加入Dt(通过Name
  2. 使用额外的索引列 (gIdx),只考虑按顺序排列的前(包括)行 - 这会产生一种累积效应(因为没有更好的术语)
  3. 计算 UniqueFunds(...) 列 - 注意在第二种情况下完成的额外子集 - n_usplit(FundType[ActivityType == "Sale"])
  4. 删除无关的索引列 (i.gIdx)。

由于使用笛卡尔连接,我不确定这将如何扩展,因此希望您的真实数据集不是数百万行。


数据:

library(data.table)
##
dt <- fread('
Name      ActivityType     FundType  UniqueFunds(AllTypes) UniqueFunds(SaleOnly)         
John       Email               a            1                     0
John       Sale                a;b          2                     2 
John       Webinar             c;d          4                     2
John       Sale                b            4                     2
John       Webinar             e            5                     2
John       Conference          b;d          5                     2
John       Sale                b;e          5                     3
Tom        Email               a            1                     0
Tom        Sale                a;b          2                     2 
Tom        Webinar             c;d          4                     2
Tom        Sale                b            4                     2
Tom        Webinar             e            5                     2
Tom        Conference          b;d          5                     2
Tom        Sale                b;e;f        6                     4                     
            ', header = TRUE)

【讨论】:

  • 非常感谢您的回答!
  • 我必须将“FundType”列从因子转换为字符,对吗?这是我为使最终代码正常工作所必须做的。
  • @gibbz00 是的,对不起,我认为它们不是因素(我在会话中默认关闭了此功能)。
  • 为什么我们在代码的最后一行键入 ["Name,i.gIdx,i.ActivityType,i.FundType"]?谢谢。
  • 这样除了两个计算字段之外,这些列也被保留。
【解决方案3】:

我实现了你想要的如下:

library(data.table)
library(stringr)
dt <- data.table(read.table(text='

                 Name      ActivityType     FundType  UniqueFunds(AllTypes) UniqueFunds(SaleOnly)         
                 John       Email               a            1                     0
                 John       Sale                a;b          2                     2 
                 John       Webinar             c;d          4                     2
                 John       Sale                b            4                     2
                 John       Webinar             e            5                     2
                 John       Conference          b;d          5                     2
                 John       Sale                b;e          5                     3
                 Tom        Email               a            1                     0
                 Tom        Sale                a;b          2                     2 
                 Tom        Webinar             c;d          4                     2
                 Tom        Sale                b            4                     2
                 Tom        Webinar             e            5                     2
                 Tom        Conference          b;d          5                     2
                 Tom        Sale                b;e;f        6                     4                    

                 ', header=T, row.names = NULL))

dt[,UniqueFunds.AllTypes. := NULL][,UniqueFunds.SaleOnly. := NULL]

#Get the different Fund Types
vals <- unique(unlist(str_extract_all(dt$FundType,"[a-z]")))

#Construct a new set of columns indicating which fund types are present
dt[,vals:=data.table(1*t(sapply(FundType,str_detect,vals))),with=FALSE]

#Calculate UniqueFunds.AllTypes
dt[, UniqueFunds.AllTypes. := 
     rowSums(sapply(.SD, cummax)), .SDcols = vals, by = Name]

#Calculate only when ActicityType == "Sale" and use cummax to achieve desired output
dt[,UniqueFunds.SaleOnly. := 0
   ][ActivityType == "Sale", UniqueFunds.SaleOnly. := 
     rowSums(sapply(.SD, cummax)), .SDcols = vals, by = Name
   ][,UniqueFunds.SaleOnly. := cummax(UniqueFunds.SaleOnly.), by = Name
     ]

#Cleanup vals
dt[,vals := NULL, with = FALSE]

【讨论】:

  • 非常感谢您的回答!
猜你喜欢
  • 1970-01-01
  • 2021-06-19
  • 1970-01-01
  • 2012-02-11
  • 2020-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多