I think median is useful here, although you need to apply it differently based on the 3 general cases you'll encounter
case 1: distribution of values do not overlap
case 2: distribution of values in 1 group completely overlaps with distribution of values in other group
case 3: distribution of values partially overlap (the data example you gave)
How you'll need to apply the median in the different cases follows
case 1: median value of all values
case 2: median value of all values
case 3: median value of only overlapping values
Your data and your plot function
plotfun <- function(df) {
with(df, boxplot(Values~Groups))
with(df, points(Values~Groups, cex=2))
}
df<-data.frame(Values = c(1,2,3,4,5,6,7,10,4,5.5,6,6.5,8,9,12),
Groups = c(rep("a",8),rep("b",7)))
df
plotfun(df)
The workhorse function is myfun. It will determine which of the 3 cases is relevant, and then apply the median accordingly. You can supply an additional argument unitofchange in case your values are not integers. That is, perhaps you're dealing with data that increments by 0.1.
library(dplyr)
myfun <- function(df, unitofchange=1) {
unitofchange <- unitofchange / 10
require(dplyr)
summarydf <- df %>%
group_by(Groups) %>%
summarise(min = min(Values), max = max(Values)) %>%
arrange(min)
if (summarydf$max[1] < summarydf$min[2]) {
# Case 1: distributions do not overlap
ans <- list(Break = median(df$Values), Nout = 0)
} else if (summarydf$max[1] > summarydf$max[2]) {
# Case 2: one distribution is completely between other distribution
ans <- list(Break = median(df$Values))
ans[["Break"]] <- modifyiftie(df, unitofchange, ans[["Break"]])
ans["Nout"] <- sum(df$Values < ans[["Break"]])
} else {
# Case 3: distributions partially overlap
subset_df <- df %>%
filter(between(Values, summarydf$min[2], summarydf$max[1]))
ans <- list(Break = median(subset_df$Values))
ans[["Break"]] <- modifyiftie(df, unitofchange, ans[["Break"]])
ans["Nout"] <- sum(subset_df$Values[subset_df$Groups == summarydf$Groups[1]] > ans[["Break"]],
subset_df$Values[subset_df$Groups == summarydf$Groups[1]] < ans[["Break"]])
}
return(ans)
}
I also included another function modifyiftie in cases like the example you gave where the separation value is found in both groups
modifyiftie <- function(df, unitofchange, b) {
require(dplyr)
tie <- df %>%
group_by(Groups) %>%
filter(Values == b)
if (nrow(tie) > 0 & all(unique(tie$Groups) %in% unique(df$Groups))) { # tie is true
return(b + unitofchange)
} else {
return(b)
}
}
Output of the 3 different cases
Case 3: Your data
df<-data.frame(Values = c(1,2,3,4,5,6,7,10,4,5.5,6,6.5,8,9,12),
Groups = c(rep("a",8),rep("b",7)))
df
myfun(df)
# $Break
# [1] 6.1
# $Nout
# [1] 5
Case 1: Distributions do not overlap
set.seed(1)
df<-data.frame(Values = c(runif(10)*10, (runif(10)*10)+10),
Groups = rep(c("a","b"), each=10))
plotfun(df)
myfun(df)
# $Break
# [1] 10.60616
# $Nout
# [1] 0
Case 2: Distribution of one group falls in between distribution of other group
set.seed(1)
df<-data.frame(Values = c((runif(10)*5)+5, runif(10)*20),
Groups = rep(c("a","b"), each=10))
plotfun(df)
myfun(df)
# $Break
# [1] 8.22478
# $Nout
# [1] 10