【问题标题】:Fastest way to check for unique values and returning it if there is only one unique value in an R data.table如果 R data.table 中只有一个唯一值,则检查唯一值并返回它的最快方法
【发布时间】:2020-08-17 04:50:49
【问题描述】:

假设我有一个大的data.table,它看起来像下面的dt

dt <- data.table(
  player_1 = c("a", "b", "b", "c"),
  player_1_age = c(10, 20, 20, 30),
  player_2 = c("b", "a", "c", "a"),
  player_2_age = c(20, 10, 30, 10)
)
# dt
#    player_1 player_1_age player_2 player_2_age
# 1:        a           10        b           20
# 2:        b           20        a           10
# 3:        b           20        c           30
# 4:        c           30        a           10

从上面的dt,我想创建一个data.table,其中包含独特的球员和他们的年龄,如下所示,player_dt

# player_dt
# player  age
#      a   10
#      b   20
#      c   30

为此,我尝试了下面的代码,但在我的较大数据集上花费的时间太长,可能是因为我正在为 sapply 的每次迭代创建一个 data.table

您如何获得上面的player_dt同时检查每个player 是否只有一个唯一的age

# get unique players
player <- sort(unique(c(dt$player_1, dt$player_2)))

# for each player, get their age, if there is only one age value
age <- sapply(player, function(x) {
  unique_values <- unique(c(
    dt[player_1 == x][["player_1_age"]],
    dt[player_2 == x][["player_2_age"]]))
  if(length(unique_values) > 1) stop() else return(unique_values)
})

# combine to create the player_dt
player_dt <- data.table(player, age)

【问题讨论】:

  • 您可能正在寻找某种形式的更长时间的支点
  • 如果有b = 15 代表player_2 或单个d = 5 代表其他玩家,你能显示预期的输出吗?
  • 谢谢@markus!如果b = 15d = 5,我希望函数停止,这样我可以更正更大数据集中的错误。在更大的数据集中,每个玩家应该只匹配一个年龄值。基本上,我想找到一种方法来识别此类错误。您是否认为我应该分两步执行此操作,(1)识别更大数据集中的错误;然后 (2) 创建唯一的玩家数据,player_dt?
  • @user12256545 您能否详细说明枢轴“更长”是什么意思?
  • Pivot longer 是赋予 pivot 或 unpivot(SQL 术语)或 melt 或 cast(重塑术语)的一些新名称。

标签: r data.table


【解决方案1】:

我已更改您的数据,以便至少发现一个错误:

library(tidyverse)

dt <- tibble(
  player_1 = c("a", "b", "b", "c"),
  player_1_age = c(10, 20, 20, 30),
  player_2 = c("b", "a", "c", "a"),
  player_2_age = c(20, 10, 30, 11)
)
  # Get the Names columns and the Age columns
colName <- names(dt)
ageCol <- colName[str_detect(colName, "age$")]
playrCol <- colName[! str_detect(colName, "age$")]

  # Gather the Ages
ages <- dt %>% 
  select(ageCol) %>% 
  gather(player_age, age)

  # Gather the names
names <- dt %>% 
  select(playrCol ) %>% 
  gather(player_name, name)

  # Bind the two together, and throw out the duplicates
  # If there are no contradictions, this is what you want.
allNameAge <- cbind( names, ages) %>% 
  select(name, age) %>% 
  distinct() %>% 
  arrange(name)

  # But check for inconsistencies.  This should leave you with
  # an empty tibble, but instead it shows the error.
inconsistencies <- allNameAge %>% 
  group_by(name) %>% 
  mutate(AGE.COUNT = n_distinct(age)) %>% 
  filter(AGE.COUNT > 1) %>% 
  ungroup()

这应该扩展到更多的名称/年龄列对。

【讨论】:

    【解决方案2】:

    我使用来自@DavidT 的数据作为输入。

    dt
    #   player_1 player_1_age player_2 player_2_age
    #1:        a           10        b           20
    #2:        b           20        a           10
    #3:        b           20        c           30
    #4:        c           30        a           11 # <--
    

    TL;DR

    你可以的

    nm <- names(dt)
    idx <- endsWith(nm, "age")
    colsAge <- nm[idx]
    colsOther <- nm[!idx]
    
    out <-
      unique(melt(
        dt,
        measure.vars = list(colsAge, colsOther),
        value.name = c("age", "player")
      )[, .(age, player)])[, if (.N == 1) # credit: https://stackoverflow.com/a/34427944/8583393
        .SD, by = player]
    out
    #   player age
    #1:      b  20
    #2:      c  30
    

    一步一步

    您可以做的是同时融合多个列 - 以"age" 结尾的列和不以"age" 结尾的列。

    nm <- names(dt)
    idx <- endsWith(nm, "age")
    colsAge <- nm[idx]
    colsOther <- nm[!idx]
    dt1 <- melt(dt, measure.vars = list(colsAge, colsOther), value.name = c("age", "player"))
    

    结果是

    dt1
    #   variable age player
    #1:        1  10      a
    #2:        1  20      b
    #3:        1  20      b
    #4:        1  30      c
    #5:        2  20      b
    #6:        2  10      a
    #7:        2  30      c
    #8:        2  11      a
    

    现在我们打电话给unique ...

    out <- unique(dt1[, .(age, player)])
    out
    #   age player
    #1:  10      a
    #2:  20      b
    #3:  30      c
    #4:  11      a
    

    ... 并过滤长度等于 1 的 "player"

    out <- out[, if(.N == 1) .SD, by=player]
    out
    #   player age
    #1:      b  20
    #2:      c  30
    

    鉴于 OP 的输入数据,不需要最后一步。

    数据

    library(data.table)
    dt <- data.table(
      player_1 = c("a", "b", "b", "c"),
      player_1_age = c(10, 20, 20, 30),
      player_2 = c("b", "a", "c", "a"),
      player_2_age = c(20, 10, 30, 11)
    )
    

    参考:https://cran.r-project.org/web/packages/data.table/vignettes/datatable-reshape.html

    【讨论】:

    • 我从您的回答中学到了很多。谢谢!使用if (.N == 1) .SD,您应该得到两个 subsetS 的 data.table,其观察次数 = 1,对吧?你知道为什么将这两个子集组合成一个两行数据表,即你的out,而不是两个单行数据表吗?也就是说,我们为什么不分别获取您的out 的第1 行和您的out 的第2 行?只是好奇使用by.SD 的输出是如何工作的
    • @johnc 很高兴听到这个答案对您有帮助。关于.SD,这个答案可能对你有帮助:stackoverflow.com/a/8509301/8583393
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多