【问题标题】:Combining variables together into a list将变量组合成一个列表
【发布时间】:2013-02-20 16:36:22
【问题描述】:

伙计们,

我被以下挑战难住了。我有一个如下所示的数据集:

BuyerID    Fruit.1     Fruit.2    Fruit.3    Amount.1    Amount.2    Amount.3
879        Banana      Apple                 4           3
765        Strawberry  Apple      Orange     1           2           4
123        Orange      Banana                1           1           1
 11        Strawberry                        3
773        Kiwi        Banana                1           2

我想做的是简化数据(如果可能的话)并折叠“Fruit”和“Amount”变量

BuyerID    Fruit                             Amount      Total    Count
879        "Banana" "Apple"                  4  3            7        2
765        "Strawberry" "Apple" "Orange"     1  2  4         7        3
123        "Orange" "Banana"                 1  1  1         3        2
 11        "Strawberry"                      3               3        1
773        "Kiwi" "Banana"                   1  2            3        2

我尝试过使用 c() 和 rbind(),但它们没有产生我想要的结果 - 我在这里尝试了提示:data.frame rows to a list,但我不太确定这是否是最好的方法简化我的数据。

这大概是为了让我更容易处理更少的变量来计算某些项目的出现(例如 60% 的买家购买香蕉)。

我希望这是可行的 - 我也愿意接受任何建议。任何解决方案表示赞赏!

谢谢。

【问题讨论】:

  • 您可能想要使用data.table 包:data.frames 每个单元格只能处理一个值。
  • 这看起来是经典的从宽到长 reshape 解决方案的不错选择。 @AnandaMahto - 你在哪里? ;-)
  • @SeñorO -- data.frames 也可以有列表,它不仅仅是一个 data.table 的东西。定义时你只需要有点创意。例如z <- data.frame(x = 1:5, y = I(lapply(seq_len(5),seq_len))
  • 以及列表列的 SO 参考 stackoverflow.com/questions/9547518/…
  • @SeñorO 我的回答演示了使用data.frame 单元处理向量以及为什么可以这样做(请注意这是一个坏主意)

标签: r list variables dataframe


【解决方案1】:

尝试复制您的数据,并使用 data.table

DT  <- data.frame(
  BuyerID = c(879,765,123,11,773), 
  Fruit.1 = c('Banana','Strawberry','Orange','Strawberry','Kiwi'),
  Fruit.2 = c('Apple','Apple','Banana',NA,'Banana'),
  Fruit.3 = c( NA, 'Orange',NA,NA,NA),
  Amount.1 = c(4,1,1,3,1), Amount.2 = c(3,2,1,NA,2), Amount.3 = c(NA,4,1,NA,NA),
  Total = c(7,7,3,3,3), 
  Count = c(2,3,2,1,2), 
  stringsAsFactors = FALSE)

# reshaping to long form and data.table

library(data.table)
DTlong <- data.table(reshape(DT, varying = list(Fruit = 2:4, Amount = 5:7), 
  direction = 'long'))

# create lists (without NA values)
# also adding count and total columns 
# by using <- to save Fruit and Amount for later use

DTlist <- DTlong[, list(Fruit <- list(as.vector(na.omit(Fruit.1))), 
                        Amount <- list(as.vector(na.omit(Amount.1))), 
                        Count  = length(unlist(Fruit)),
                        Total = sum(unlist(Amount))), 
                 by = BuyerID]

  BuyerID                      V1    V2 Count Total
1:     879            Banana,Apple   4,3     2     7
2:     765 Strawberry,Apple,Orange 1,2,4     3     7
3:     123           Orange,Banana 1,1,1     2     3
4:      11              Strawberry     3     1     3
5:     773             Kiwi,Banana   1,2     2     3

@RicardoSaporta 编辑:

如果您愿意,可以使用list(list(c(....)))跳过重塑步骤
这可能会节省相当多的执行时间(缺点是它添加了NAs 而不是空格)。但是,正如@Marius 指出的那样,上面的DTlong 可能更容易使用。

DT <- data.table(DT)
DT[,   Fruit := list(list(c(  Fruit.1,   Fruit.2,   Fruit.3))), by=BuyerID]
DT[, Ammount := list(list(c(Amount.1, Amount.2, Amount.3))), by=BuyerID]

# Or as a single line
DT[,   list(  Fruit = list(c( Fruit.1,  Fruit.2,  Fruit.3)), 
            Ammount = list(c(Amount.1, Amount.2, Amount.3)), 
            Total, Count),  # other columns used
            by = BuyerID]

【讨论】:

  • @jacatra:虽然此解决方案可以让您到达您说想要的位置,但我可以建议长格式数据框 DTlong(作为此答案的中间步骤创建)会容易得多长期合作?
  • @mnel,我根据您的解决方案添加了一个编辑。希望你不介意
  • @Ricardo Saporta 不错的补充 +1
【解决方案2】:

这是一个非常糟糕的主意,但它位于基础data.frame 中。它之所以有效,是因为data.frame 实际上是一个等长向量的列表。您可以强制data.frame 将向量存储在单元格中,但这需要一些技巧。我建议其他格式,包括 Marius 的建议或列表。

DT <- data.frame(
  BuyerID = c(879,765,123,11,773), 
  Fruit.1 = c('Banana','Strawberry','Orange','Strawberry','Kiwi'),
  Fruit.2 = c('Apple','Apple','Banana',NA,'Banana'),
  Fruit.3 = c( NA, 'Orange',NA,NA,NA),
  Amount.1 = c(4,1,1,3,1), Amount.2 = c(3,2,1,NA,2), Amount.3 = c(NA,4,1,NA,NA),
  stringsAsFactors = FALSE)

DT2 <- DT[, 1, drop=FALSE]
DT2$Fruit <- apply(DT[, 2:4], 1, function(x) unlist(na.omit(x)))
DT2$Amount <- apply(DT[, 5:7], 1, function(x) unlist(na.omit(x)))
DT2$Total <- sapply(DT2$Amount, sum)
DT2$Count <- sapply(DT2$Fruit, length)

产量:

> DT2
  BuyerID                     Fruit  Amount Total Count
1     879             Banana, Apple    4, 3     7     2
2     765 Strawberry, Apple, Orange 1, 2, 4     7     3
3     123            Orange, Banana 1, 1, 1     3     2
4      11                Strawberry       3     3     1
5     773              Kiwi, Banana    1, 2     3     2

【讨论】:

  • 可能,但强迫这是微妙的。不确定。
  • 我认为 list(1:3,1:3,1:2) 是一个长度为 3 的向量,所以没关系
  • length(list(1:3,1:3,1:2)) 是 3,所以关于等长向量的点是无效的。我同意列表列处理起来可能很尴尬,所以除了作为一个书呆子之外,我可能真的没有任何意义!
【解决方案3】:

这里有一个解决方案,带有基本包。它类似于 Tyler 解决方案,但只需一次应用。

res <- apply(DT,1,function(x){
  data.frame(Fruit= paste(na.omit(x[2:4]),collapse=' '),
             Amount = paste(na.omit(x[5:7]),collapse =','),
             Total = sum(as.numeric(na.omit(x[5:7]))),
             Count = length(na.omit(x[2:4])))
})
do.call(rbind,res)
                    Fruit  Amount Total Count
1            Banana Apple    4, 3     7     2
2 Strawberry Apple Orange 1, 2, 4     7     3
3           Orange Banana 1, 1, 1     3     2
4              Strawberry       3     3     1
5             Kiwi Banana    1, 2     3     2

我也会通过 grep 更改索引号,类似这样

 Fruit  = gregexpr('Fruit[.][0-9]', colnames(dat)) > 0  
 Amount = gregexpr('Amount[.][0-9]', colnames(dat)) > 0 

 x[2:4] replace by x[which(Fruit)]....

EDIT添加一些基准测试。

library(microbenchmark)
library(data.table)
microbenchmark(ag(),mn(), am(), tr())
Unit: milliseconds
  expr       min        lq    median        uq       max
1 ag() 11.584522 12.268140 12.671484 13.317934 109.13419
2 am()  9.776206 10.515576 10.798504 11.437938 137.44867
3 mn()  6.470190  6.805646  6.974797  7.290722  48.68571
4 tr()  1.759771  1.929870  2.026960  2.142066   7.06032

对于一个小的 data.frame,Tyler Rinker 是赢家!!我如何解释这个(只是猜测)

  1. data:table 解决方案受到使用 reshape 的影响,通常 data.table 对于大数据来说更快。
  2. Ag 研究解决方案速度较慢,因为每行都有子集,不像 Tyler 解决方案在使用之前应用哪个子集。
  3. am 解决方案很慢,因为使用了 reshape 和 merge..

【讨论】:

  • +1 没错,reshape 是一个非 data.table 函数,所以 mn() 不是纯 data.table 解决方案。
  • @agstudy 不要忘记你选择了paste,它比unlist 慢。
  • 我认为您无法使用这种技术将向量放入细胞中。您所拥有的是单元格中的一个字符串,这很容易做到,但是您已经失去了将向量作为数字向量的向量轻松操作的能力;例如你不能做sapply(Amount, max),因为你现在有字符向量。
  • @TylerRinker 是的,我明白你的意思。在这里使用 paste 是一种解决方法。如果他想要一个原子向量或单个字符串,即使 OP 也不精确。
【解决方案4】:

除了已经存在的很好的答案,这里还有另一个(坚持基础 R):

with(DT, {
  # Convert to long format
  DTlong <- reshape(DT, direction = "long", 
                    idvar = "BuyerID", varying = 2:ncol(DT))
  # aggregate your fruit columns 
  # You need the `do.call(data.frame, ...)` to convert
  #   the resulting matrix-as-a-column into separate columns
  Agg1 <- do.call(data.frame, 
                  aggregate(Fruit ~ BuyerID, DTlong,
                            function(x) c(Fruit = paste0(x, collapse = " "),
                                          Count = length(x))))
  # aggregate the amount columns
  Agg2 <- aggregate(Amount ~ BuyerID, DTlong, sum)
  # merge the results
  merge(Agg1, Agg2)
})
#   BuyerID             Fruit.Fruit Fruit.Count Amount
# 1      11              Strawberry           1      3
# 2     123           Orange Banana           2      3
# 3     765 Strawberry Apple Orange           3      7
# 4     773             Kiwi Banana           2      3
# 5     879            Banana Apple           2      7

基本概念是:

  1. 使用reshape 获取长格式数据(实际上,我认为您应该停下来)
  2. 使用两种不同的aggregate 命令,一种用于聚合您的水果列,另一种用于聚合您的金额列。 aggregate 的公式方法负责删除 NA,但您可以使用 na.action 参数指定所需的行为。
  3. 使用merge 将两者结合起来。

【讨论】:

    【解决方案5】:

    问这个问题时它不存在,但tidyr 很适合这个。

    重用来自@mnel 答案的数据,

    library(tidyr)
    separator <- ' '
    DT %>%
      unite(Fruit, grep("Fruit", names(.)), sep = separator) %>%
      unite(Amount, grep("Amount", names(.)), sep = separator)
    
    #   BuyerID                   Fruit  Amount Total Count
    # 1     879         Banana Apple NA  4 3 NA     7     2
    # 2     765 Strawberry Apple Orange   1 2 4     7     3
    # 3     123        Orange Banana NA   1 1 1     3     2
    # 4      11        Strawberry NA NA 3 NA NA     3     1
    # 5     773          Kiwi Banana NA  1 2 NA     3     2
    

    【讨论】:

      猜你喜欢
      • 2021-10-26
      • 1970-01-01
      • 2013-03-09
      • 2018-10-23
      • 1970-01-01
      • 2021-11-08
      • 2022-08-18
      • 1970-01-01
      • 2023-04-06
      相关资源
      最近更新 更多