我有一个数字变量,范围从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"))
答案 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%向下舍入,但每个中点以相同的概率向上和向下舍入。除非您的数据非常少且随机抽取异常偏差,否则应该足够接近。