在一个表里有一个升序排序后的数值型字段,需要找出一个“TOP N”的位置,在此位置前数据之间的差距很小,而此位置后数据之间的差距突然变大。下面的栗子中,前面十几位的数值分别是在0.11、0.12、0.13、0.14、0.15缓慢增长,第18个数字变成了0.47,和前面的相比差距很大,此时要找到的“TOP N”就是17。

楼主暂时只根据 AI 给的建议先将就用着一个比较粗暴的方法,就是计算这串序列的差分,在一个 for 循环里,找到第一个差分值大于均值+标准差的位置。请教坛子里的小伙伴们有没撒好办法?

library(echarts4r)

data <- data.frame(rank_asc = c(1:30),
                     values = c(
 0.116161,	0.116812,	0.1179149,	0.1187652,	0.1197417,	0.120519,	0.1208446,	0.1303933,	0.1303933,	0.1303933,	0.1336485,	0.1341733,	0.1352761,	0.1431509,	0.151769,	0.1577087,	0.1577087,	0.4789998,	0.4808422,	0.4831949,	0.488747,	0.4965138,	0.4969146,	0.4972559,	0.4993662,	0.4998289,	0.5006878,	0.5020363,	0.5024971,	0.5025391))

# 绘制折线图
data |>
  e_charts(rank_asc) |>
  e_line(values)

first_diff <- function(values) {
  # 对一组按从小到大排序的数值计算差分
  differences <- diff(values)
  # 找到第一个差值大于平均值加上标准差的位置
  for (i in 2:(length(differences) - 1)) {
    if (differences[i] > mean(differences[1:i]) + sd(differences[1:i]) * 3) {
      #cat("距离突然变大的位置是", i, "\n")
      return(i)
      break
    }
  }
}

first_diff(data$values)

本平庸程序员最后让 AI 给帮忙改成了这样,倒真是挺实用的。

first_diff <- function(values) {
  differences <- diff(values)
  mean_diff <- cumsum(differences) / (1:length(differences))
  sd_diff <-
    sqrt(cumsum((differences - mean_diff) ^ 2) / (1:length(differences)))
  
  result <- 10  # 默认返回值
  
  for (threshold in c(3, 2, 1)) {
    for (i in 2:(length(differences) - 1)) {
      if (differences[i] > mean_diff[i] + threshold * sd_diff[i]) {
        if (i < 20) {
          result <- i  # 存储返回值,而不是直接返回
          break
        }
      }
    }
  }
  
  # 如果第一个差分值比接下来19个差分值都大,返回1
  if (is.na(i) == FALSE & differences[1] > max(differences[2:20])) {
    return(1)
  }
  return(result)  # 返回存储的值
}

我想到个方法,新建一列,用lag函数推迟一位,再减去原来的列,然后用max函数找到位置

    Claireasstronaut

    我想了想,原始需求并不是想要找到一串序列中,前后两位差距最大的位置,而是要从前往后找到差距突然变得比之前大的。

    这个问题的业务场景是酱紫的,有一个目标样本,要从待匹配的十万个样本中,找到跟目标最相似的几个,现在我已经给这十万个样本分别计算好了和目标样本的相似度并且排好序,由于只是想找到最相似的几个,所以直接用 MAX 可能会使匹配得到的结果范围太大了。

      先做一个一维聚类,然后找到最近类别的样本就可以了。

      set.seed(42)
      
      x <- sample(c(
        rnorm(20, mean = -2, sd = 0.1),
        rnorm(20, mean = -1, sd = 0.25),
        rnorm(20, mean = 0, sd = 0.3),
        rnorm(20, mean = 1, sd = 0.25),
        rnorm(20, mean = 2, sd = 0.1)
      ))
      
      cl <- oneclust::oneclust(x, 5)
      
      target <- 1.1
      idx <- which(cl$cluster == cl$cluster[which.min(abs(x - target))])
      x[idx]

      “突然”好像不是那么好定义的……也许你的18个数开始是一些0.47, 0.48的,但是如果从第30个数开始变成了4.7,那么是不是又显得原先第18个数的变化不够“突然”了呢?感觉这个需求像是个变点检测的问题。

      当然,顺着一开始的问题说的话,这个像是在找一个二阶差分比较大的点?先把原数据做一次差分,得到依次的两两差值;然后把这个差值再做一次差分,就得到了差值的差值。这其中取最大的一个,就可以对应到升序幅度差异最大的那个位置了。

      回到后面的实际需求的话,相似度都算好了,要么就别搞这种自适应的N了,直接拍脑袋一个N的值拉倒~样本量如果真的够多够接近,那这种自适应的N带来的收益,相对于一个拍脑袋的N,恐怕有限。

        fenguoerbian

        我也想直接让用户拍脑袋定个 N 算逑,但是用户觉得咱们这不是在建模型咩,为撒还总得拍脑袋,哈哈。

        待会我把一维聚类,还有二阶差分都试试看。另外,变点检测这个词是俺第一回听说,感觉好像就是这个,是俺孤陋寡闻了。

        yuanfan 而是要从前往后找到差距突然变得比之前大的。

        想到斜率,斜率发生很大变化的地方。

        猛一看是不是求一下增长率就可以了。

        2 个月 后