基于R

时间:2018-04-05 13:55:25

标签: r

我有一个数字变量,范围从1(min)到5(max)。该值的范围包括8个不同的变量。因此,第一行看起来像这样:

Var1 Var2 Var3 Var4 Var5 Var6 Var7 Var8
4    4    1    4    5    4    4    1

我已计算(逐行)8个变量中每行的中值。偶尔,中位数将是一个中点值,例如4.5(因为它是偶数个变量)。因此,生成的行可能如下所示:

Var1 Var2 Var3 Var4 Var5 Var6 Var7 Var8 Median
1    2    3    4    5    5    5    5    4.5

当我在Medina变量计算的每个中值上调用表格时,我会得到这个:

    table(df$Median)
  1 1.5   2 2.5   3 3.5   4 4.5   5 
  2   3  10   5  25  17  75  53  87 

我想克服的问题是我希望"摆脱"通过将它们包含在最近的非十进制值中来表示中点/十进制值;但是,如果我只是使用round(),那么我最终会偏移这些值(根据定义,4.5实际上是介于两者之间),如下所示:

table(round(df$Median))
1   2   3   4   5 
2  18  25 145  87 

我想做的是根据表格中非十进制数字的比例舍入值(不包括中点值):

所以我会使用dplyr过滤函数获得非十进制数的比例:

df %>% filter(median %% 1 == 0) %>% 
select(median) %>% table() %>% prop.table()

获得:

         1          2          3          4          5 
0.01005025 0.05025126 0.12562814 0.37688442 0.43718593

下一步需要构建一个函数,该函数将获取中值变量中的所有中点值,并将它们四舍五入到最接近的非十进制值,同时保持非十进制变量的比例保持不变或接近原始值。例如,4.5最接近的值是4和5,因此它有可能基于比例0.37688442变为4,基于比例0.43718593变为5。这样我就可以将中点值转换为整体值;但是,它不会像使用简单round()那样有偏见。

另一种方法是将值平均分配在4到5之间。因此,值为4.5的变量的50%将变为4,50%将变为5。

我感谢任何有助于我解决这个问题的建议,或者我可以开始开发这个功能。

EDIT1。我自己尝试回答这个问题。

EDIT2。提供数据。

dput(head(df, 15))

 structure(list(uniqueID = c("R_AtXpiwxKPvILFv3", "R_2xwP4iz6UAu1fTj", 
"R_b8IXGRKHP58x7GR", "R_ZelynHN8PCxxYyt", "R_PNjIc7h4dHebRgR", 
"R_2bTZvYLUuKNC22D", "R_3iLqwuDs493HstB", "R_291dITimLKjYXeL", 
"R_YWWGleFLxlIYzrX", "R_3st91vjNWNXlTHt", "R_3Mm8P52gaaxIpwD", 
"R_3MxHXTnrncpgnB8", "R_1LqDx1uxReOQHvO", "R_vJEGJDmbqdfO7qF", 
"R_3q8Wl8qys6nqxBH"), Median = c(4, 4.5, 
1, 4, 5, 4.5, 4, 1.5, 4.5, 4, 3.5, 2, 4.5, 4.5, 3.5)), .Names = c("uniqueID", 
"Median"), row.names = c(NA, -15L), class = c("tbl_df", 
"tbl", "data.frame"))

2 个答案:

答案 0 :(得分:1)

根据评论的建议,我试图创建一个从所有中间值中随机加0.1或减0.1的函数。它不是最优雅的功能,但它完成了这项工作。该方法的一个问题可能是通过随机抽样数据集的一部分并向其添加0.1来实现随机化。因此,剩余的非抽样分数自动减去0.1。单独为每个值执行此操作会更优雅,但我必须探索此选项。

功能:

randomize_midpoint <- function(dataset, new_random_median) {
  # Prepare variable for mutate
  new_random_median <- enquo(new_random_median)
  # Get Sample A
  sample_A <- dataset %>%
    filter(Median %% 1 != 0) %>% # get midpoint values
    sample_frac(0.5, replace = F) %>% # randomly sample 50% of them
    select(uniqueID, Median) # anti_join will need some unique identifier
  # Get Sample B by anti_join
  sample_B <- dataset %>%
    filter(Median %% 1 != 0) %>%
    anti_join(sample_A) %>% # anti_join automatically uses uniqueID
    select(uniqueID, Median)
  # Create opposite of %in%
  "%w/o%" <- Negate("%in%")
  # Mutate median according to conditions in case_when()
  dataset %>% mutate(
    !!quo_name(new_random_median) := case_when(
      uniqueID %in% sample_A$uniqueID ~ round(Median + 0.1),
      uniqueID %in% sample_B$uniqueID ~ round(Median - 0.1),
      uniqueID %w/o% c(sample_A$uniqueID , sample_B$uniqueID) ~ Median
    )
  )
}

要与之前的table()进行比较的函数的输出:

  randomize_midpoint(dataset = df, new_random_median = random_med) %>%
  select(random_med) %>%
  table()

将返回:

 Joining, by = c("uniqueID", "Median")
  1   2   3   4   5 
  2  16  36 110 113

上表:

table(round(df$Median))
1   2   3   4   5 
2  18  25 145  87 

答案 1 :(得分:1)

我是这样实现的:

round_randomly = function(x, tolerance = 1e-6) {
    round(x + sample(c(-tolerance, tolerance), size = length(x), replace = TRUE))
}

调用您的示例数据dd

table(round_randomly(dd$Median))
# 1 2 4 5 
# 1 2 8 4 

如果您的数据仅为整数且0.5,则任何小于0.5的tolerance值都将起作用。如果你有更多的连续数据,更小的容差更好(防止4.4,因为抖动到4.51并被舍入到5)。我将默认值设置为1e-6,这似乎是合理的,值为&gt; 4.499999可能会向上舍入到5。

你的答案在向中点添加一个随机值时会遇到很多麻烦 - 由于四舍五入,这是不必要的。如果原始值为4,则4.000001仍将舍入为4.即使将公差设置为0.4,4.4仍会舍入为4)。

我的方法无法保证将完全 50%的中点向上舍入和50%向下舍入,但每个中点以相同的概率向上和向下舍入。除非您的数据非常少且随机抽取异常偏差,否则应该足够接近。