Skip to content

Instantly share code, notes, and snippets.

@scbrown86
Forked from mrdwab/stratified.R
Last active February 16, 2021 15:29
Show Gist options
  • Save scbrown86/e7b7947e93f3ff22ab77a751e69ea1fc to your computer and use it in GitHub Desktop.
Save scbrown86/e7b7947e93f3ff22ab77a751e69ea1fc to your computer and use it in GitHub Desktop.
Stratified random sampling from a `data.frame` in R
stratified <- function(df, group, size, select = NULL,
replace = TRUE, bothSets = FALSE) {
if (is.null(select)) {
df <- df
} else {
if (is.null(names(select))) stop("'select' must be a named list")
if (!all(names(select) %in% names(df)))
stop("Please verify your 'select' argument")
temp <- sapply(names(select),
function(x) df[[x]] %in% select[[x]])
df <- df[rowSums(temp) == length(select), ]
}
df.interaction <- interaction(df[group], drop = TRUE)
df.table <- table(df.interaction)
df.split <- split(df, df.interaction)
if (length(size) > 1) {
if (length(size) != length(df.split))
stop("Number of groups is ", length(df.split),
" but number of sizes supplied is ", length(size))
if (is.null(names(size))) {
n <- setNames(size, names(df.split))
message(sQuote("size"), " vector entered as:\n\nsize = structure(c(",
paste(n, collapse = ", "), "),\n.Names = c(",
paste(shQuote(names(n)), collapse = ", "), ")) \n\n")
} else {
ifelse(all(names(size) %in% names(df.split)),
n <- size[names(df.split)],
stop("Named vector supplied with names ",
paste(names(size), collapse = ", "),
"\n but the names for the group levels are ",
paste(names(df.split), collapse = ", ")))
}
} else if (size < 1) {
n <- round(df.table * size, digits = 0)
} else if (size >= 1) {
if (all(df.table >= size) || isTRUE(replace)) {
n <- setNames(rep(size, length.out = length(df.split)),
names(df.split))
} else {
message(
"Some groups\n---",
paste(names(df.table[df.table < size]), collapse = ", "),
"---\ncontain fewer observations",
" than desired number of samples.\n",
"All observations have been returned from those groups.")
n <- c(sapply(df.table[df.table >= size], function(x) x = size),
df.table[df.table < size])
}
}
temp <- lapply(
names(df.split),
function(x) df.split[[x]][sample(df.table[x],
n[x], replace = replace), ])
set1 <- do.call("rbind", temp)
if (isTRUE(bothSets)) {
set2 <- df[!rownames(df) %in% rownames(set1), ]
list(SET1 = set1, SET2 = set2)
} else {
set1
}
}
@scbrown86
Copy link
Author

From the original

The arguments to stratified are:
df: The input data.frame
group: A character vector of the column or columns that make up the "strata".
size: The desired sample size.

  • If size is a value less than 1, a proportionate sample is taken from each stratum.
  • If size is a single integer of 1 or more, that number of samples is taken from each stratum.
  • If size is a vector of integers, the specified number of samples is taken for each stratum. It is recommended that you use a named vector.
    For example, if you have two strata, "A" and "B", and you wanted 5 samples from "A" and 10 from "B", you would enter size = c(A = 5, B = 10).

select: This allows you to subset the groups in the sampling process. This is a list. For instance, if your group variable was "Group", and it contained three strata, "A", "B", and "C", but you only wanted to sample from "A" and "C", you can use select = list(Group = c("A", "C")).

replace: For sampling with replacement.

@scbrown86
Copy link
Author

Creating a re-sampling index for caret

nBootstraps <- 10
rsIndex <-  replicate(nBootstraps, ## Number of bootstrap samples
                      ## Stratifying function that will select (with replacement) 80% of the minimum observed class size from all classes
                      as.integer(rownames(stratified(classData, "ClimZone", floor(0.8*min(table(classData$ClimZone)))))),
                      simplify = FALSE)
length(rsIndex)
names(rsIndex) <- paste0("Fold", 1:nBootstraps)
head(rsIndex); tail(rsIndex)

By setting size = 80% of the minimum observed class frequency, we can ensure that there will always be samples for each class in the test folds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment