From b85d763e906dd5bfa74551bbfea86a5e16084570 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 10:37:36 -0700 Subject: [PATCH 01/13] Ignore developer files. --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 145eac0..190e120 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,11 @@ .RData .Ruserdata .Rproj.user +.lintr vignettes/figures *.pdf -*.html \ No newline at end of file +*.html + +*.code-workspace +.vscode From 68e44a6c812b4b9ca877cb8619e9691d7eaaf53f Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 12:15:41 -0700 Subject: [PATCH 02/13] Add a method to concatenate elements in a vector, formatted with an oxford comma. --- R/utility.R | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/R/utility.R b/R/utility.R index e780345..0e57f06 100644 --- a/R/utility.R +++ b/R/utility.R @@ -62,3 +62,19 @@ get.encoded.distance <- function(points) { return(encoded.distances); } + + +oxford.comma.vector.conat <- function(vec, empty.value = '', flatten.empty.value = TRUE) { + if (length(vec) == 0) { + if (flatten.empty.value) { + oxford.comma.vector.conat(empty.value, flatten.empty.value = FALSE) + } else { + empty.value + } + } else if (length(vec) == 1) { + vec + } else { + sep <- if (length(vec) > 2) ', and ' else ' and '; + paste(paste(head(vec, -1), collapse = ', '), tail(vec, 1), sep = sep); + } + } From 529a3c3db1d977c209f80359096c2825f3dcaa74 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 12:16:28 -0700 Subject: [PATCH 03/13] Update `...distribution.plot` with a more accurate error message during data.frame validation. --- R/create.clone.genome.distribution.plot.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/create.clone.genome.distribution.plot.R b/R/create.clone.genome.distribution.plot.R index dbc52fe..ca53e2b 100644 --- a/R/create.clone.genome.distribution.plot.R +++ b/R/create.clone.genome.distribution.plot.R @@ -12,8 +12,10 @@ create.clone.genome.distribution.plot <- function( ) { # Preprocess ---------------------------------------------------------------------------------- - if (!all(c('chr', 'pos', 'clone.id') %in% names(snv.df))) { - stop('snv.df does not contain at least one of chr, pos or clone.id columns') + required.cols <- c('chr', 'pos', 'clone.id'); + missing.cols <- required.cols[!(required.cols %in% names(snv.df))]; + if (length(missing.cols) != 0) { + stop(paste0('snv.df must contain the columns ', oxford.comma.vector.conat(required.cols),'; snv.df is missing ', oxford.comma.vector.conat(missing.cols, toString(required.cols)))) } if (is.null(clone.order)) { clone.order <- sort(unique(snv.df$clone.id)); From 76847c2d1dfca8bab542d3def862f8a67cb85bf0 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 12:40:35 -0700 Subject: [PATCH 04/13] Add missing semicolon to utility function. Also add a period in error message. --- R/create.clone.genome.distribution.plot.R | 2 +- R/utility.R | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/create.clone.genome.distribution.plot.R b/R/create.clone.genome.distribution.plot.R index ca53e2b..b4ad332 100644 --- a/R/create.clone.genome.distribution.plot.R +++ b/R/create.clone.genome.distribution.plot.R @@ -15,7 +15,7 @@ create.clone.genome.distribution.plot <- function( required.cols <- c('chr', 'pos', 'clone.id'); missing.cols <- required.cols[!(required.cols %in% names(snv.df))]; if (length(missing.cols) != 0) { - stop(paste0('snv.df must contain the columns ', oxford.comma.vector.conat(required.cols),'; snv.df is missing ', oxford.comma.vector.conat(missing.cols, toString(required.cols)))) + stop(paste0('snv.df must contain the columns ', oxford.comma.vector.conat(required.cols),'; snv.df is missing ', oxford.comma.vector.conat(missing.cols, toString(required.cols)), '.')) } if (is.null(clone.order)) { clone.order <- sort(unique(snv.df$clone.id)); diff --git a/R/utility.R b/R/utility.R index 0e57f06..4022dd0 100644 --- a/R/utility.R +++ b/R/utility.R @@ -69,10 +69,10 @@ oxford.comma.vector.conat <- function(vec, empty.value = '', flatten.empty.value if (flatten.empty.value) { oxford.comma.vector.conat(empty.value, flatten.empty.value = FALSE) } else { - empty.value + empty.value; } } else if (length(vec) == 1) { - vec + vec; } else { sep <- if (length(vec) > 2) ', and ' else ' and '; paste(paste(head(vec, -1), collapse = ', '), tail(vec, 1), sep = sep); From a438db3a40682ea8f80eb5640149c38fae706b0d Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 12:53:02 -0700 Subject: [PATCH 05/13] Change gated checks for null values in DF for `data.frame.to.array`. --- R/data.frame.to.array.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/data.frame.to.array.R b/R/data.frame.to.array.R index 2f31f7d..433a28a 100644 --- a/R/data.frame.to.array.R +++ b/R/data.frame.to.array.R @@ -5,8 +5,10 @@ data.frame.to.array <- function( y.axis = 'ID' ) { - if (is.null(DF[[x.axis]]) | is.null(DF[[value]]) | is.null(DF[[y.axis]])) { - stop(paste('Dataframe does not contain one of the columns:', value, x.axis, y.axis)); + required.cols <- c(value, x.axis, y.axis); + missing.cols <- required.cols[!(required.cols %in% names(DF))]; + if (length(missing.cols) != 0) { + stop(paste0('Dataframe must contain the columns: ', oxford.comma.vector.conat(required.cols), '; Dataframe is missing ', oxford.comma.vector.conat(missing.cols, toString(required.cols)), '.')); } arr <- reshape( data = DF[, c(x.axis, y.axis, value)], From 14169b7943511a1671fde723ac474ab3b82f9a9b Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 12:55:21 -0700 Subject: [PATCH 06/13] Fix line length issues. --- R/create.clone.genome.distribution.plot.R | 6 +++++- R/data.frame.to.array.R | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/R/create.clone.genome.distribution.plot.R b/R/create.clone.genome.distribution.plot.R index b4ad332..e0eb2ec 100644 --- a/R/create.clone.genome.distribution.plot.R +++ b/R/create.clone.genome.distribution.plot.R @@ -15,7 +15,11 @@ create.clone.genome.distribution.plot <- function( required.cols <- c('chr', 'pos', 'clone.id'); missing.cols <- required.cols[!(required.cols %in% names(snv.df))]; if (length(missing.cols) != 0) { - stop(paste0('snv.df must contain the columns ', oxford.comma.vector.conat(required.cols),'; snv.df is missing ', oxford.comma.vector.conat(missing.cols, toString(required.cols)), '.')) + stop(paste0( + 'snv.df must contain the columns ', + oxford.comma.vector.conat(required.cols), + '; snv.df is missing ', + oxford.comma.vector.conat(missing.cols, toString(required.cols)), '.')); } if (is.null(clone.order)) { clone.order <- sort(unique(snv.df$clone.id)); diff --git a/R/data.frame.to.array.R b/R/data.frame.to.array.R index 433a28a..4e89b35 100644 --- a/R/data.frame.to.array.R +++ b/R/data.frame.to.array.R @@ -8,7 +8,11 @@ data.frame.to.array <- function( required.cols <- c(value, x.axis, y.axis); missing.cols <- required.cols[!(required.cols %in% names(DF))]; if (length(missing.cols) != 0) { - stop(paste0('Dataframe must contain the columns: ', oxford.comma.vector.conat(required.cols), '; Dataframe is missing ', oxford.comma.vector.conat(missing.cols, toString(required.cols)), '.')); + stop(paste0( + 'Dataframe must contain the columns: ', + oxford.comma.vector.conat(required.cols), + '; Dataframe is missing ', + oxford.comma.vector.conat(missing.cols, toString(required.cols)), '.')); } arr <- reshape( data = DF[, c(x.axis, y.axis, value)], From 097de33348aa6457085ed935e50442f9e8ba6f44 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 13:01:22 -0700 Subject: [PATCH 07/13] Fix typo in `concat` name. --- R/create.clone.genome.distribution.plot.R | 4 ++-- R/data.frame.to.array.R | 4 ++-- R/utility.R | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/create.clone.genome.distribution.plot.R b/R/create.clone.genome.distribution.plot.R index e0eb2ec..a6a5cad 100644 --- a/R/create.clone.genome.distribution.plot.R +++ b/R/create.clone.genome.distribution.plot.R @@ -17,9 +17,9 @@ create.clone.genome.distribution.plot <- function( if (length(missing.cols) != 0) { stop(paste0( 'snv.df must contain the columns ', - oxford.comma.vector.conat(required.cols), + oxford.comma.vector.concat(required.cols), '; snv.df is missing ', - oxford.comma.vector.conat(missing.cols, toString(required.cols)), '.')); + oxford.comma.vector.concat(missing.cols, toString(required.cols)), '.')); } if (is.null(clone.order)) { clone.order <- sort(unique(snv.df$clone.id)); diff --git a/R/data.frame.to.array.R b/R/data.frame.to.array.R index 4e89b35..681475e 100644 --- a/R/data.frame.to.array.R +++ b/R/data.frame.to.array.R @@ -10,9 +10,9 @@ data.frame.to.array <- function( if (length(missing.cols) != 0) { stop(paste0( 'Dataframe must contain the columns: ', - oxford.comma.vector.conat(required.cols), + oxford.comma.vector.concat(required.cols), '; Dataframe is missing ', - oxford.comma.vector.conat(missing.cols, toString(required.cols)), '.')); + oxford.comma.vector.concat(missing.cols, toString(required.cols)), '.')); } arr <- reshape( data = DF[, c(x.axis, y.axis, value)], diff --git a/R/utility.R b/R/utility.R index 4022dd0..f2c32f8 100644 --- a/R/utility.R +++ b/R/utility.R @@ -64,10 +64,10 @@ get.encoded.distance <- function(points) { } -oxford.comma.vector.conat <- function(vec, empty.value = '', flatten.empty.value = TRUE) { +oxford.comma.vector.concat <- function(vec, empty.value = '', flatten.empty.value = TRUE) { if (length(vec) == 0) { if (flatten.empty.value) { - oxford.comma.vector.conat(empty.value, flatten.empty.value = FALSE) + oxford.comma.vector.concat(empty.value, flatten.empty.value = FALSE) } else { empty.value; } From 2754107997c3c149945d252fb2a2cf434df981e6 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 14:37:12 -0700 Subject: [PATCH 08/13] Fix error with concat not returning a string. --- R/utility.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utility.R b/R/utility.R index f2c32f8..c30ec6d 100644 --- a/R/utility.R +++ b/R/utility.R @@ -72,7 +72,7 @@ oxford.comma.vector.concat <- function(vec, empty.value = '', flatten.empty.valu empty.value; } } else if (length(vec) == 1) { - vec; + toString(vec); } else { sep <- if (length(vec) > 2) ', and ' else ' and '; paste(paste(head(vec, -1), collapse = ', '), tail(vec, 1), sep = sep); From 2fc23f31e610bfbf7bbbae5aa4eeb055c1eb272a Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 14:37:45 -0700 Subject: [PATCH 09/13] Add tests for oxford comma concat. --- tests/testthat/test-utility.R | 64 +++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/testthat/test-utility.R b/tests/testthat/test-utility.R index aeb18c7..a0c89fe 100644 --- a/tests/testthat/test-utility.R +++ b/tests/testthat/test-utility.R @@ -76,3 +76,67 @@ test_that( expect_equal(order(result), expected.order); }); + +test_that( + 'oxford.comma.vector.concat returns `empty.value` when input is empty', { + vec <- NULL; + expect_equal(oxford.comma.vector.concat(vec), ''); + expect_equal(oxford.comma.vector.concat(vec, 'default'), 'default'); + vec <- c(NULL); + expect_equal(oxford.comma.vector.concat(vec), ''); + expect_equal(oxford.comma.vector.concat(vec, 'default'), 'default'); + vec <- c(NULL, NULL); + expect_equal(oxford.comma.vector.concat(vec), ''); + expect_equal(oxford.comma.vector.concat(vec, 'default'), 'default'); + vec <- c(); + expect_equal(oxford.comma.vector.concat(vec), ''); + expect_equal(oxford.comma.vector.concat(vec, 'default'), 'default'); + }); + +test_that( + 'oxford.comma.vector.concat returns `empty.value` in correct format when input is empty', { + vec <- NULL; + expect_equal(oxford.comma.vector.concat(vec, flatten.empty.value = FALSE), ''); + expect_equal(oxford.comma.vector.concat(vec, 'default', flatten.empty.value = FALSE), 'default'); + vec <- c(NULL); + expect_equal(oxford.comma.vector.concat(vec, flatten.empty.value = FALSE), ''); + expect_equal(oxford.comma.vector.concat(vec, 'default', flatten.empty.value = FALSE), 'default'); + vec <- c(NULL, NULL); + expect_equal(oxford.comma.vector.concat(vec, flatten.empty.value = FALSE), ''); + expect_equal(oxford.comma.vector.concat(vec, 'default', flatten.empty.value = FALSE), 'default'); + vec <- c(); + expect_equal(oxford.comma.vector.concat(vec, flatten.empty.value = FALSE), ''); + expect_equal(oxford.comma.vector.concat(vec, 'default', flatten.empty.value = FALSE), 'default'); + + vec <- c(); + vec_default <- NULL; + expect_null(oxford.comma.vector.concat(vec, vec_default, flatten.empty.value = FALSE)); + vec <- c(); + vec_default <- c(1, 2, 3); + expect_equal(oxford.comma.vector.concat(vec, vec_default), '1, 2, and 3'); + expect_equal(oxford.comma.vector.concat(vec, vec_default, flatten.empty.value = FALSE), c(1, 2, 3)); + }); + +test_that( + 'oxford.comma.vector.concat returns grammatically correct oxford comma', { + vec <- c(1); + expect_equal(oxford.comma.vector.concat(vec), '1'); + vec <- c(1, 2); + expect_equal(oxford.comma.vector.concat(vec), '1 and 2'); + vec <- c(1, 2, 3); + expect_equal(oxford.comma.vector.concat(vec), '1, 2, and 3'); + vec <- c(1, 2, 3, 4); + expect_equal(oxford.comma.vector.concat(vec), '1, 2, 3, and 4'); + }); + +test_that( + 'oxford.comma.vector.concat returns grammatically correct oxford comma for flattened default value', { + vec_default <- c(1); + expect_equal(oxford.comma.vector.concat(NULL, vec_default), '1'); + vec_default <- c(1, 2); + expect_equal(oxford.comma.vector.concat(NULL, vec_default), '1 and 2'); + vec_default <- c(1, 2, 3); + expect_equal(oxford.comma.vector.concat(NULL, vec_default), '1, 2, and 3'); + vec_default <- c(1, 2, 3, 4); + expect_equal(oxford.comma.vector.concat(NULL, vec_default), '1, 2, 3, and 4'); + }); From 9046ed931791c49d8e9b86247854188c481dc536 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 14:47:14 -0700 Subject: [PATCH 10/13] Add tests for `...distribution.plot` data.frame validation. --- ...st-create.clone.genome.distribution.plot.R | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/testthat/test-create.clone.genome.distribution.plot.R diff --git a/tests/testthat/test-create.clone.genome.distribution.plot.R b/tests/testthat/test-create.clone.genome.distribution.plot.R new file mode 100644 index 0000000..cd98d1f --- /dev/null +++ b/tests/testthat/test-create.clone.genome.distribution.plot.R @@ -0,0 +1,22 @@ +test_that('create.clone.genome.distribution.plot enforces data.frame columns', { + snv.df <- data.frame(); + expect_error( + create.clone.genome.distribution.plot(snv.df), + 'must contain the columns chr, pos, and clone.id; snv.df is missing chr, pos, and clone.id.' + ); + snv.df <- data.frame(chr = 1); + expect_error( + create.clone.genome.distribution.plot(snv.df), + 'must contain the columns chr, pos, and clone.id; snv.df is missing pos and clone.id.' + ); + snv.df <- data.frame(chr = 1, pos = 1); + expect_error( + create.clone.genome.distribution.plot(snv.df), + 'must contain the columns chr, pos, and clone.id; snv.df is missing clone.id.' + ); + snv.df <- data.frame(clone.id = 1); + expect_error( + create.clone.genome.distribution.plot(snv.df), + 'must contain the columns chr, pos, and clone.id; snv.df is missing chr and pos.' + ); +}); From af255a315427bef8fe0224daa66c6cf03cc5e556 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Mon, 12 May 2025 15:19:27 -0700 Subject: [PATCH 11/13] Add tests for `data.frame.to.array`. --- tests/testthat/test-data.frame.to.array.R | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/testthat/test-data.frame.to.array.R diff --git a/tests/testthat/test-data.frame.to.array.R b/tests/testthat/test-data.frame.to.array.R new file mode 100644 index 0000000..9a11c07 --- /dev/null +++ b/tests/testthat/test-data.frame.to.array.R @@ -0,0 +1,22 @@ +test_that('data.frame.to.array enforces data.frame columns', { + DF <- data.frame(); + expect_error( + data.frame.to.array(DF), + 'Dataframe must contain the columns: CCF, SNV.id, and ID; Dataframe is missing CCF, SNV.id, and ID.' + ); + DF <- data.frame(CCF = 1); + expect_error( + data.frame.to.array(DF), + 'Dataframe must contain the columns: CCF, SNV.id, and ID; Dataframe is missing SNV.id and ID.' + ); + DF <- data.frame(CCF = 1, SNV.id = 1); + expect_error( + data.frame.to.array(DF), + 'Dataframe must contain the columns: CCF, SNV.id, and ID; Dataframe is missing ID.' + ); + DF <- data.frame(SNV.id = 1); + expect_error( + data.frame.to.array(DF), + 'Dataframe must contain the columns: CCF, SNV.id, and ID; Dataframe is missing CCF and ID.' + ); +}); From 974d1df3a09ceb6129532ec5f9769b0c0accceee Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Tue, 13 May 2025 09:57:40 -0700 Subject: [PATCH 12/13] Fix variable formatting error. --- tests/testthat/test-utility.R | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/testthat/test-utility.R b/tests/testthat/test-utility.R index a0c89fe..76284f1 100644 --- a/tests/testthat/test-utility.R +++ b/tests/testthat/test-utility.R @@ -109,12 +109,12 @@ test_that( expect_equal(oxford.comma.vector.concat(vec, 'default', flatten.empty.value = FALSE), 'default'); vec <- c(); - vec_default <- NULL; - expect_null(oxford.comma.vector.concat(vec, vec_default, flatten.empty.value = FALSE)); + vec.default <- NULL; + expect_null(oxford.comma.vector.concat(vec, vec.default, flatten.empty.value = FALSE)); vec <- c(); - vec_default <- c(1, 2, 3); - expect_equal(oxford.comma.vector.concat(vec, vec_default), '1, 2, and 3'); - expect_equal(oxford.comma.vector.concat(vec, vec_default, flatten.empty.value = FALSE), c(1, 2, 3)); + vec.default <- c(1, 2, 3); + expect_equal(oxford.comma.vector.concat(vec, vec.default), '1, 2, and 3'); + expect_equal(oxford.comma.vector.concat(vec, vec.default, flatten.empty.value = FALSE), c(1, 2, 3)); }); test_that( @@ -131,12 +131,12 @@ test_that( test_that( 'oxford.comma.vector.concat returns grammatically correct oxford comma for flattened default value', { - vec_default <- c(1); - expect_equal(oxford.comma.vector.concat(NULL, vec_default), '1'); - vec_default <- c(1, 2); - expect_equal(oxford.comma.vector.concat(NULL, vec_default), '1 and 2'); - vec_default <- c(1, 2, 3); - expect_equal(oxford.comma.vector.concat(NULL, vec_default), '1, 2, and 3'); - vec_default <- c(1, 2, 3, 4); - expect_equal(oxford.comma.vector.concat(NULL, vec_default), '1, 2, 3, and 4'); + vec.default <- c(1); + expect_equal(oxford.comma.vector.concat(NULL, vec.default), '1'); + vec.default <- c(1, 2); + expect_equal(oxford.comma.vector.concat(NULL, vec.default), '1 and 2'); + vec.default <- c(1, 2, 3); + expect_equal(oxford.comma.vector.concat(NULL, vec.default), '1, 2, and 3'); + vec.default <- c(1, 2, 3, 4); + expect_equal(oxford.comma.vector.concat(NULL, vec.default), '1, 2, 3, and 4'); }); From 8cb7cc99617fde39af2acff1ccec720dba512d23 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Tue, 13 May 2025 10:54:59 -0700 Subject: [PATCH 13/13] Use `collapse` in `paste` instead of using `toString` for consistency. --- R/create.clone.genome.distribution.plot.R | 2 +- R/data.frame.to.array.R | 2 +- R/utility.R | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/create.clone.genome.distribution.plot.R b/R/create.clone.genome.distribution.plot.R index a6a5cad..a3a72fd 100644 --- a/R/create.clone.genome.distribution.plot.R +++ b/R/create.clone.genome.distribution.plot.R @@ -19,7 +19,7 @@ create.clone.genome.distribution.plot <- function( 'snv.df must contain the columns ', oxford.comma.vector.concat(required.cols), '; snv.df is missing ', - oxford.comma.vector.concat(missing.cols, toString(required.cols)), '.')); + oxford.comma.vector.concat(missing.cols, paste(required.cols, collapse = ', ')), '.')); } if (is.null(clone.order)) { clone.order <- sort(unique(snv.df$clone.id)); diff --git a/R/data.frame.to.array.R b/R/data.frame.to.array.R index 681475e..48374ed 100644 --- a/R/data.frame.to.array.R +++ b/R/data.frame.to.array.R @@ -12,7 +12,7 @@ data.frame.to.array <- function( 'Dataframe must contain the columns: ', oxford.comma.vector.concat(required.cols), '; Dataframe is missing ', - oxford.comma.vector.concat(missing.cols, toString(required.cols)), '.')); + oxford.comma.vector.concat(missing.cols, paste(required.cols, collapse = ', ')), '.')); } arr <- reshape( data = DF[, c(x.axis, y.axis, value)], diff --git a/R/utility.R b/R/utility.R index c30ec6d..695d290 100644 --- a/R/utility.R +++ b/R/utility.R @@ -72,7 +72,7 @@ oxford.comma.vector.concat <- function(vec, empty.value = '', flatten.empty.valu empty.value; } } else if (length(vec) == 1) { - toString(vec); + paste(vec, collapse = ', '); } else { sep <- if (length(vec) > 2) ', and ' else ' and '; paste(paste(head(vec, -1), collapse = ', '), tail(vec, 1), sep = sep);