From c257ec518e721d2756eb8376e8824f075db13db1 Mon Sep 17 00:00:00 2001 From: Michael Mahoney Date: Wed, 29 Jun 2022 20:16:31 -0400 Subject: [PATCH] Add `group_bootstraps()` (#316) * Add group_initial_split * Add group_validation_split * Add group_validation_split() to docs * Add group_bootstraps() * Add group_bootstraps to pkgdown * Error on 0-row assessment * Test for 0-row assessment * Tiny edits to error message, docs * Update NEWS Co-authored-by: Julia Silge --- NAMESPACE | 12 ++++ NEWS.md | 2 +- R/boot.R | 100 +++++++++++++++++++++++++- R/compat-vctrs-helpers.R | 1 + R/compat-vctrs.R | 52 ++++++++++++++ R/make_groups.R | 20 ++++-- R/mc.R | 18 +++-- _pkgdown.yml | 1 + man/bootstraps.Rd | 2 +- man/group_bootstraps.Rd | 57 +++++++++++++++ tests/testthat/_snaps/boot.md | 9 +++ tests/testthat/_snaps/compat-vctrs.md | 54 ++++++++++++++ tests/testthat/_snaps/mc.md | 9 +++ tests/testthat/test-boot.R | 37 ++++++++++ tests/testthat/test-mc.R | 5 ++ 15 files changed, 367 insertions(+), 12 deletions(-) create mode 100644 man/group_bootstraps.Rd diff --git a/NAMESPACE b/NAMESPACE index 1c74cd82..12cb3db2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -74,6 +74,7 @@ S3method(vec_cast,bootstraps.data.frame) S3method(vec_cast,bootstraps.tbl_df) S3method(vec_cast,data.frame.apparent) S3method(vec_cast,data.frame.bootstraps) +S3method(vec_cast,data.frame.group_bootstraps) S3method(vec_cast,data.frame.group_mc_cv) S3method(vec_cast,data.frame.group_vfold_cv) S3method(vec_cast,data.frame.loo_cv) @@ -86,6 +87,9 @@ S3method(vec_cast,data.frame.sliding_period) S3method(vec_cast,data.frame.sliding_window) S3method(vec_cast,data.frame.validation_split) S3method(vec_cast,data.frame.vfold_cv) +S3method(vec_cast,group_bootstraps.data.frame) +S3method(vec_cast,group_bootstraps.group_bootstraps) +S3method(vec_cast,group_bootstraps.tbl_df) S3method(vec_cast,group_mc_cv.data.frame) S3method(vec_cast,group_mc_cv.group_mc_cv) S3method(vec_cast,group_mc_cv.tbl_df) @@ -118,6 +122,7 @@ S3method(vec_cast,sliding_window.sliding_window) S3method(vec_cast,sliding_window.tbl_df) S3method(vec_cast,tbl_df.apparent) S3method(vec_cast,tbl_df.bootstraps) +S3method(vec_cast,tbl_df.group_bootstraps) S3method(vec_cast,tbl_df.group_mc_cv) S3method(vec_cast,tbl_df.group_vfold_cv) S3method(vec_cast,tbl_df.loo_cv) @@ -144,6 +149,7 @@ S3method(vec_ptype2,bootstraps.data.frame) S3method(vec_ptype2,bootstraps.tbl_df) S3method(vec_ptype2,data.frame.apparent) S3method(vec_ptype2,data.frame.bootstraps) +S3method(vec_ptype2,data.frame.group_bootstraps) S3method(vec_ptype2,data.frame.group_mc_cv) S3method(vec_ptype2,data.frame.group_vfold_cv) S3method(vec_ptype2,data.frame.loo_cv) @@ -156,6 +162,9 @@ S3method(vec_ptype2,data.frame.sliding_period) S3method(vec_ptype2,data.frame.sliding_window) S3method(vec_ptype2,data.frame.validation_split) S3method(vec_ptype2,data.frame.vfold_cv) +S3method(vec_ptype2,group_bootstraps.data.frame) +S3method(vec_ptype2,group_bootstraps.group_bootstraps) +S3method(vec_ptype2,group_bootstraps.tbl_df) S3method(vec_ptype2,group_mc_cv.data.frame) S3method(vec_ptype2,group_mc_cv.group_mc_cv) S3method(vec_ptype2,group_mc_cv.tbl_df) @@ -188,6 +197,7 @@ S3method(vec_ptype2,sliding_window.sliding_window) S3method(vec_ptype2,sliding_window.tbl_df) S3method(vec_ptype2,tbl_df.apparent) S3method(vec_ptype2,tbl_df.bootstraps) +S3method(vec_ptype2,tbl_df.group_bootstraps) S3method(vec_ptype2,tbl_df.group_mc_cv) S3method(vec_ptype2,tbl_df.group_vfold_cv) S3method(vec_ptype2,tbl_df.loo_cv) @@ -208,6 +218,7 @@ S3method(vec_ptype2,vfold_cv.tbl_df) S3method(vec_ptype2,vfold_cv.vfold_cv) S3method(vec_restore,apparent) S3method(vec_restore,bootstraps) +S3method(vec_restore,group_bootstraps) S3method(vec_restore,group_mc_cv) S3method(vec_restore,group_vfold_cv) S3method(vec_restore,loo_cv) @@ -235,6 +246,7 @@ export(ends_with) export(everything) export(form_pred) export(gather) +export(group_bootstraps) export(group_initial_split) export(group_mc_cv) export(group_validation_split) diff --git a/NEWS.md b/NEWS.md index 07b74d5e..109f465c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,7 +2,7 @@ * Added arguments to control how `group_vfold_cv()` combines groups. Use `balance = "groups"` to assign (roughly) the same number of groups to each fold, or `balance = "observations"` to assign (roughly) the same number of observations to each fold. -* Added new functions for grouped resampling: `group_mc_cv()` (#313), `group_initial_split()` and `group_validation_split()` (#315). +* Added new functions for grouped resampling: `group_mc_cv()` (#313), `group_initial_split()` and `group_validation_split()` (#315), and `group_bootstraps()` (#316). * Added a new function, `reverse_splits()`, to swap analysis and assessment splits (#319, #284). diff --git a/R/boot.R b/R/boot.R index 12691712..0318d704 100644 --- a/R/boot.R +++ b/R/boot.R @@ -20,7 +20,7 @@ #' some estimators used by the `summary` function that require the apparent #' error rate. #' @export -#' @return An tibble with classes `bootstraps`, `rset`, `tbl_df`, `tbl`, and +#' @return A tibble with classes `bootstraps`, `rset`, `tbl_df`, `tbl`, and #' `data.frame`. The results include a column for the data split objects and a #' column called `id` that has a character string with the resample identifier. #' @examples @@ -146,3 +146,101 @@ boot_splits <- id = names0(length(split_objs), "Bootstrap") ) } + +#' Group Bootstraps +#' +#' Group bootstrapping creates splits of the data based +#' on some grouping variable (which may have more than a single row +#' associated with it). A common use of this kind of resampling is when you +#' have repeated measures of the same subject. +#' A bootstrap sample is a sample that is the same size as the original data +#' set that is made using replacement. This results in analysis samples that +#' have multiple replicates of some of the original rows of the data. The +#' assessment set is defined as the rows of the original data that were not +#' included in the bootstrap sample. This is often referred to as the +#' "out-of-bag" (OOB) sample. +#' @details The argument `apparent` enables the option of an additional +#' "resample" where the analysis and assessment data sets are the same as the +#' original data set. This can be required for some types of analysis of the +#' bootstrap results. +#' +#' @inheritParams bootstraps +#' @inheritParams make_groups +#' @export +#' @return An tibble with classes `group_bootstraps` `bootstraps`, `rset`, +#' `tbl_df`, `tbl`, and `data.frame`. The results include a column for the data +#' split objects and a column called `id` that has a character string with the +#' resample identifier. +#' @examplesIf rlang::is_installed("modeldata") +#' data(ames, package = "modeldata") +#' +#' set.seed(13) +#' group_bootstraps(ames, Neighborhood, times = 3) +#' group_bootstraps(ames, Neighborhood, times = 3, apparent = TRUE) +#' +#' @export +group_bootstraps <- function(data, + group, + times = 25, + apparent = FALSE, + ...) { + + rlang::check_dots_empty() + + group <- validate_group({{ group }}, data) + + split_objs <- + group_boot_splits( + data = data, + group = group, + times = times + ) + + ## We remove the holdout indices since it will save space and we can + ## derive them later when they are needed. + split_objs$splits <- map(split_objs$splits, rm_out) + + if (apparent) { + split_objs <- bind_rows(split_objs, apparent(data)) + } + + boot_att <- list( + times = times, + apparent = apparent, + strata = FALSE, + group = group + ) + + new_rset( + splits = split_objs$splits, + ids = split_objs$id, + attrib = boot_att, + subclass = c("group_bootstraps", "bootstraps", "rset") + ) +} + +group_boot_splits <- function(data, group, times = 25) { + + group <- getElement(data, group) + n <- nrow(data) + indices <- make_groups(data, group, times, balance = "prop", prop = 1, replace = TRUE) + indices <- lapply(indices, boot_complement, n = n) + split_objs <- + purrr::map(indices, make_splits, data = data, class = c("group_boot_split", "boot_split")) + all_assessable <- purrr::map(split_objs, function(x) nrow(assessment(x))) + + if (any(all_assessable == 0)) { + rlang::abort( + c( + "Some assessment sets contained zero rows", + i = "Consider using a non-grouped resampling method" + ), + call = rlang::caller_env() + ) + } + + list( + splits = split_objs, + id = names0(length(split_objs), "Bootstrap") + ) +} diff --git a/R/compat-vctrs-helpers.R b/R/compat-vctrs-helpers.R index 9699ed61..c26988ed 100644 --- a/R/compat-vctrs-helpers.R +++ b/R/compat-vctrs-helpers.R @@ -120,6 +120,7 @@ test_data <- function() { delayedAssign("rset_subclasses", { list( bootstraps = bootstraps(test_data()), + group_bootstraps = group_bootstraps(test_data(), y), vfold_cv = vfold_cv(test_data(), v = 10, repeats = 2), group_vfold_cv = group_vfold_cv(test_data(), y), loo_cv = loo_cv(test_data()), diff --git a/R/compat-vctrs.R b/R/compat-vctrs.R index 9f7082d2..7e21b89f 100644 --- a/R/compat-vctrs.R +++ b/R/compat-vctrs.R @@ -116,6 +116,58 @@ vec_cast.data.frame.bootstraps <- function(x, to, ..., x_arg = "", to_arg = "") df_cast(x, to, ..., x_arg = x_arg, to_arg = to_arg) } +# ------------------------------------------------------------------------------ +# group_bootstraps + +#' @export +vec_restore.group_bootstraps <- function(x, to, ...) { + rset_reconstruct(x, to) +} + + +#' @export +vec_ptype2.group_bootstraps.group_bootstraps <- function(x, y, ..., x_arg = "", y_arg = "") { + stop_never_called("vec_ptype2.group_bootstraps.group_bootstraps") +} +#' @export +vec_ptype2.group_bootstraps.tbl_df <- function(x, y, ..., x_arg = "", y_arg = "") { + stop_never_called("vec_ptype2.group_bootstraps.tbl_df") +} +#' @export +vec_ptype2.tbl_df.group_bootstraps <- function(x, y, ..., x_arg = "", y_arg = "") { + stop_never_called("vec_ptype2.tbl_df.group_bootstraps") +} +#' @export +vec_ptype2.group_bootstraps.data.frame <- function(x, y, ..., x_arg = "", y_arg = "") { + stop_never_called("vec_ptype2.group_bootstraps.data.frame") +} +#' @export +vec_ptype2.data.frame.group_bootstraps <- function(x, y, ..., x_arg = "", y_arg = "") { + stop_never_called("vec_ptype2.data.frame.group_bootstraps") +} + + +#' @export +vec_cast.group_bootstraps.group_bootstraps <- function(x, to, ..., x_arg = "", to_arg = "") { + stop_incompatible_cast_rset(x, to, x_arg = x_arg, to_arg = to_arg) +} +#' @export +vec_cast.group_bootstraps.tbl_df <- function(x, to, ..., x_arg = "", to_arg = "") { + stop_incompatible_cast_rset(x, to, x_arg = x_arg, to_arg = to_arg) +} +#' @export +vec_cast.tbl_df.group_bootstraps <- function(x, to, ..., x_arg = "", to_arg = "") { + tib_cast(x, to, ..., x_arg = x_arg, to_arg = to_arg) +} +#' @export +vec_cast.group_bootstraps.data.frame <- function(x, to, ..., x_arg = "", to_arg = "") { + stop_incompatible_cast_rset(x, to, x_arg = x_arg, to_arg = to_arg) +} +#' @export +vec_cast.data.frame.group_bootstraps <- function(x, to, ..., x_arg = "", to_arg = "") { + df_cast(x, to, ..., x_arg = x_arg, to_arg = to_arg) +} + # ------------------------------------------------------------------------------ # vfold_cv diff --git a/R/make_groups.R b/R/make_groups.R index d25859c8..8fd9c9d4 100644 --- a/R/make_groups.R +++ b/R/make_groups.R @@ -105,25 +105,35 @@ balance_observations <- function(data_ind, v, ...) { } -balance_prop <- function(prop, data_ind, v, ...) { +balance_prop <- function(prop, data_ind, v, replace = FALSE, ...) { rlang::check_dots_empty() - if (!is.numeric(prop) | prop >= 1 | prop <= 0) { + acceptable_prop <- is.numeric(prop) + acceptable_prop <- acceptable_prop && ((prop <= 1 && replace) || (prop < 1 && !replace)) + acceptable_prop <- acceptable_prop && prop > 0 + if (!acceptable_prop) { rlang::abort("`prop` must be a number on (0, 1).", call = rlang::caller_env()) } n_obs <- nrow(data_ind) freq_table <- vec_count(data_ind$..group) + n <- nrow(freq_table) + # If sampling with replacement, + # set `n` to the number of resamples we'd need + # if we somehow got the smallest group every time + if (replace) n <- n * prop * sum(freq_table$count) / min(freq_table$count) + n <- ceiling(n) + freq_table <- purrr::map_dfr( seq_len(v), function(x) { - freq_table <- freq_table[sample.int(nrow(freq_table)), ] - cumulative_proportion <- cumsum(freq_table$count) / sum(freq_table$count) + work_table <- freq_table[sample.int(nrow(freq_table), n, replace = replace), ] + cumulative_proportion <- cumsum(work_table$count) / sum(freq_table$count) crosses_target <- which(cumulative_proportion > prop)[[1]] is_closest <- cumulative_proportion[c(crosses_target, crosses_target - 1)] is_closest <- which.min(abs(is_closest - prop)) - 1 crosses_target <- crosses_target - is_closest - out <- freq_table[seq_len(crosses_target), ] + out <- work_table[seq_len(crosses_target), ] out$assignment <- x out } diff --git a/R/mc.R b/R/mc.R index 63fd9bd9..39bfc7fc 100644 --- a/R/mc.R +++ b/R/mc.R @@ -170,8 +170,7 @@ group_mc_cv <- function(data, group, prop = 3 / 4, times = 25, ...) { data = data, group = group, prop = prop, - times = times, - balance = "prop" + times = times ) ## We remove the holdout indices since it will save space and we can @@ -193,14 +192,25 @@ group_mc_cv <- function(data, group, prop = 3 / 4, times = 25, ...) { ) } -group_mc_splits <- function(data, group, prop = 3 / 4, times = 25, balance = "prop") { +group_mc_splits <- function(data, group, prop = 3 / 4, times = 25) { group <- getElement(data, group) n <- nrow(data) - indices <- make_groups(data, group, times, balance, prop = prop) + indices <- make_groups(data, group, times, balance = "prop", prop = prop, replace = FALSE) indices <- lapply(indices, mc_complement, n = n) split_objs <- purrr::map(indices, make_splits, data = data, class = "grouped_mc_split") + all_assessable <- purrr::map(split_objs, function(x) nrow(assessment(x))) + + if (any(all_assessable == 0)) { + rlang::abort( + c( + "Some assessment sets contained zero rows", + i = "Consider using a non-grouped resampling method" + ), + call = rlang::caller_env() + ) + } list( splits = split_objs, id = names0(length(split_objs), "Resample") diff --git a/_pkgdown.yml b/_pkgdown.yml index 26131304..70ab1945 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -41,6 +41,7 @@ reference: - loo_cv - mc_cv - validation_split + - group_bootstraps - group_mc_cv - group_vfold_cv - rolling_origin diff --git a/man/bootstraps.Rd b/man/bootstraps.Rd index 4c94a554..9cc6f61c 100644 --- a/man/bootstraps.Rd +++ b/man/bootstraps.Rd @@ -39,7 +39,7 @@ error rate.} \item{...}{Not currently used.} } \value{ -An tibble with classes \code{bootstraps}, \code{rset}, \code{tbl_df}, \code{tbl}, and +A tibble with classes \code{bootstraps}, \code{rset}, \code{tbl_df}, \code{tbl}, and \code{data.frame}. The results include a column for the data split objects and a column called \code{id} that has a character string with the resample identifier. } diff --git a/man/group_bootstraps.Rd b/man/group_bootstraps.Rd new file mode 100644 index 00000000..3ddf79a8 --- /dev/null +++ b/man/group_bootstraps.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/boot.R +\name{group_bootstraps} +\alias{group_bootstraps} +\title{Group Bootstraps} +\usage{ +group_bootstraps(data, group, times = 25, apparent = FALSE, ...) +} +\arguments{ +\item{data}{A data frame.} + +\item{group}{A variable in \code{data} (single character or name) used for +grouping observations with the same value to either the analysis or +assessment set within a fold.} + +\item{times}{The number of bootstrap samples.} + +\item{apparent}{A logical. Should an extra resample be added where the +analysis and holdout subset are the entire data set. This is required for +some estimators used by the \code{summary} function that require the apparent +error rate.} + +\item{...}{Not currently used.} +} +\value{ +An tibble with classes \code{group_bootstraps} \code{bootstraps}, \code{rset}, +\code{tbl_df}, \code{tbl}, and \code{data.frame}. The results include a column for the data +split objects and a column called \code{id} that has a character string with the +resample identifier. +} +\description{ +Group bootstrapping creates splits of the data based +on some grouping variable (which may have more than a single row +associated with it). A common use of this kind of resampling is when you +have repeated measures of the same subject. +A bootstrap sample is a sample that is the same size as the original data +set that is made using replacement. This results in analysis samples that +have multiple replicates of some of the original rows of the data. The +assessment set is defined as the rows of the original data that were not +included in the bootstrap sample. This is often referred to as the +"out-of-bag" (OOB) sample. +} +\details{ +The argument \code{apparent} enables the option of an additional +"resample" where the analysis and assessment data sets are the same as the +original data set. This can be required for some types of analysis of the +bootstrap results. +} +\examples{ +\dontshow{if (rlang::is_installed("modeldata")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +data(ames, package = "modeldata") + +set.seed(13) +group_bootstraps(ames, Neighborhood, times = 3) +group_bootstraps(ames, Neighborhood, times = 3, apparent = TRUE) +\dontshow{\}) # examplesIf} +} diff --git a/tests/testthat/_snaps/boot.md b/tests/testthat/_snaps/boot.md index 71dcb985..a73a6d74 100644 --- a/tests/testthat/_snaps/boot.md +++ b/tests/testthat/_snaps/boot.md @@ -1,3 +1,12 @@ +# bad args + + Code + group_bootstraps(warpbreaks, tension) + Condition + Error in `group_bootstraps()`: + ! Some assessment sets contained zero rows + i Consider using a non-grouped resampling method + # printing Code diff --git a/tests/testthat/_snaps/compat-vctrs.md b/tests/testthat/_snaps/compat-vctrs.md index 16f66cb6..05530556 100644 --- a/tests/testthat/_snaps/compat-vctrs.md +++ b/tests/testthat/_snaps/compat-vctrs.md @@ -9,6 +9,17 @@ * `splits` -> `splits...3` * `id` -> `id...4` +--- + + Code + expect_s3_class_bare_tibble(vec_cbind(x, x)) + Message + New names: + * `splits` -> `splits...1` + * `id` -> `id...2` + * `splits` -> `splits...3` + * `id` -> `id...4` + --- Code @@ -199,6 +210,49 @@ * `splits` -> `splits...3` * `id` -> `id...4` +--- + + Code + expect_identical(vec_cbind(x, x), vec_cbind(tbl, tbl)) + Message + New names: + * `splits` -> `splits...1` + * `id` -> `id...2` + * `splits` -> `splits...3` + * `id` -> `id...4` + New names: + * `splits` -> `splits...1` + * `id` -> `id...2` + * `splits` -> `splits...3` + * `id` -> `id...4` + +--- + + Code + expect_identical(vec_cbind(x, tbl), vec_cbind(tbl, tbl)) + Message + New names: + * `splits` -> `splits...1` + * `id` -> `id...2` + * `splits` -> `splits...3` + * `id` -> `id...4` + New names: + * `splits` -> `splits...1` + * `id` -> `id...2` + * `splits` -> `splits...3` + * `id` -> `id...4` + +--- + + Code + expect_s3_class_bare_tibble(vec_cbind(x, x)) + Message + New names: + * `splits` -> `splits...1` + * `id` -> `id...2` + * `splits` -> `splits...3` + * `id` -> `id...4` + --- Code diff --git a/tests/testthat/_snaps/mc.md b/tests/testthat/_snaps/mc.md index 5faa7cac..ce0c5554 100644 --- a/tests/testthat/_snaps/mc.md +++ b/tests/testthat/_snaps/mc.md @@ -19,6 +19,15 @@ 10 Resample10 # ... with 15 more rows +# grouping - bad args + + Code + group_mc_cv(warpbreaks, group = "tension", prop = 0.99) + Condition + Error in `group_mc_cv()`: + ! Some assessment sets contained zero rows + i Consider using a non-grouped resampling method + # grouping - printing Code diff --git a/tests/testthat/test-boot.R b/tests/testthat/test-boot.R index abc06114..34dd6f9c 100644 --- a/tests/testthat/test-boot.R +++ b/tests/testthat/test-boot.R @@ -33,6 +33,38 @@ test_that("apparent", { expect_equal(res2, dat1) }) +test_that("groups", { + dat1 <- data.frame(a = 1:20, b = letters[1:20], c = rep(1:10, 2)) + set.seed(11) + rs1 <- group_bootstraps(dat1, c) + sizes1 <- dim_rset(rs1) + + expect_true(all(sizes1$analysis == nrow(dat1))) + same_data <- + purrr::map_lgl(rs1$splits, function(x) { + all.equal(x$data, dat1) + }) + expect_true(all(same_data)) + + good_holdout <- purrr::map_lgl( + rs1$splits, + function(x) { + length(intersect(x$in_ind, x$out_id)) == 0 + } + ) + expect_true(all(good_holdout)) + + dat1 <- data.frame(a = 1:20, b = letters[1:20], c = rep(1:4, 5)) + rs2 <- bootstraps(dat1, apparent = TRUE) + sizes2 <- dim_rset(rs2) + + expect_true(all(sizes2$analysis == nrow(dat1))) + expect_true(all(sizes2$assessment[nrow(sizes2)] == nrow(dat1))) + expect_equal(sizes2$assessment[sizes2$id == "Apparent"], nrow(dat1)) + res2 <- + as.data.frame(rs2$splits[[nrow(sizes2)]], data = "assessment") + expect_equal(res2, dat1) +}) test_that("strata", { set.seed(11) @@ -73,6 +105,11 @@ test_that("strata", { test_that("bad args", { expect_error(bootstraps(warpbreaks, strata = warpbreaks$tension)) expect_error(bootstraps(warpbreaks, strata = c("tension", "wool"))) + set.seed(1) + expect_snapshot( + group_bootstraps(warpbreaks, tension), + error = TRUE + ) }) diff --git a/tests/testthat/test-mc.R b/tests/testthat/test-mc.R index 424bdbb9..e91a2c61 100644 --- a/tests/testthat/test-mc.R +++ b/tests/testthat/test-mc.R @@ -95,6 +95,11 @@ test_that("grouping - bad args", { expect_error(group_mc_cv(warpbreaks, group = "tensio")) expect_error(group_mc_cv(warpbreaks)) expect_error(group_mc_cv(warpbreaks, group = "tension", balance = "groups")) + set.seed(1) + expect_snapshot( + group_mc_cv(warpbreaks, group = "tension", prop = 0.99), + error = TRUE + ) }) test_that("grouping - default param", {