From a97c13c3f1944a566a7f04bb2169834d51a3fbc9 Mon Sep 17 00:00:00 2001 From: pbrohan Date: Mon, 24 Jul 2023 17:48:11 +0100 Subject: [PATCH 1/7] Use vctrs::vec_as_names --- DESCRIPTION | 3 +- R/read_ods.R | 54 ++++++++++++--------------------- tests/testthat/test_as_tibble.R | 17 +++++------ 3 files changed, 29 insertions(+), 45 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 30e2136..43f9fe3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -29,7 +29,8 @@ Imports: stringi, utils, zip, - tibble + tibble, + vctrs (>= 0.4.2) LinkingTo: cpp11 Suggests: diff --git a/R/read_ods.R b/R/read_ods.R index 0885436..25d3767 100644 --- a/R/read_ods.R +++ b/R/read_ods.R @@ -1,4 +1,4 @@ -.change_df_with_col_row_header <- function(x, col_header, row_header) { +.change_df_with_col_row_header <- function(x, col_header, row_header, .name_repair) { if((nrow(x) < 2 && col_header )|| (ncol(x) < 2 && row_header)) { warning("Cannot make column/row names if this would cause the dataframe to be empty.", call. = FALSE) return(x) @@ -7,8 +7,11 @@ jcol <- ifelse(row_header, 2, 1) g <- x[irow:nrow(x), jcol:ncol(x), drop=FALSE] # maintain as dataframe for single column + + rownames(g) <- if(row_header) x[seq(irow, nrow(x)), 1] else NULL # don't want character row headers given by 1:nrow(g) - colnames(g) <- if(col_header) x[1, seq(jcol, ncol(x))] else cellranger::num_to_letter(seq_len(ncol(g))) + col_n <- if(col_header) x[1, seq(jcol, ncol(x))] else c(rep("", ncol(x))) + colnames(g) <- vctrs::vec_as_names(unlist(col_n), repair = .name_repair) return(g) } @@ -70,10 +73,8 @@ range = NULL, row_names = FALSE, strings_as_factors = FALSE, - check_names = FALSE, verbose = FALSE, - as_tibble = FALSE, - .name_repair = "check_unique") { + as_tibble = TRUE) { if (missing(path) || !is.character(path)) { stop("No file path was provided for the 'path' argument. Please provide a path to a file to import.", call. = FALSE) } @@ -92,23 +93,14 @@ if (!is.logical(strings_as_factors)) { stop("strings_as_factors must be of type `boolean`", call. = FALSE) } - if (!is.logical(check_names)) { - stop("check_names must be of type `boolean`", call. = FALSE) - } if (!is.logical(verbose)) { stop("verbose must be of type `boolean`", call. = FALSE) } if (!is.logical(as_tibble)) { stop("as_tibble must be of type `boolean", call. = FALSE) } - if (as_tibble && - (!(.name_repair %in% c("minimal", - "unique", - "check_unique", - "universal")) || - is.function(.name_repair))) { - stop(".name_repair must either be one of \"minimal\", \"unique\", \"check_unique\", \"univseral\" or a function", call. = FALSE) - } + else + {} } .read_ods <- function(path, @@ -121,25 +113,22 @@ range = NULL, row_names = FALSE, strings_as_factors = FALSE, - check_names = FALSE, verbose = FALSE, - as_tibble = FALSE, - .name_repair = "check_unique", + as_tibble = TRUE, + .name_repair = "unique", flat = FALSE) { .check_read_args(path, sheet, col_names, col_types, na, - skip, + skip, formula_as_formula, range, row_names, strings_as_factors, - check_names, verbose, - as_tibble, - .name_repair) + as_tibble) # Get cell range info limits <- .standardise_limits(range, skip) # Get sheet number. @@ -202,8 +191,8 @@ ncol = strtoi(strings[1]), byrow = TRUE), stringsAsFactors = FALSE) - res <- .change_df_with_col_row_header(res, col_names, row_names) - res <- data.frame(res, check.names = check_names) + res <- .change_df_with_col_row_header(res, col_names, row_names, .name_repair) + res <- data.frame(res) if (inherits(col_types, 'col_spec')) { res <- readr::type_convert(df = res, col_types = col_types, na = na) } else if (length(col_types) == 0 && is.null(col_types)) { @@ -243,7 +232,6 @@ #' @param range selection of rectangle using Excel-like cell range, such as \code{range = "D12:F15"} or \code{range = "R1C12:R6C15"}. Cell range processing is handled by the \code{\link[=cellranger]{cellranger}} package. #' @param row_names logical, indicating whether the file contains the names of the rows as its first column. Default is FALSE. #' @param strings_as_factors logical, if character columns to be converted to factors. Default is FALSE. -#' @param check_names logical, passed down to base::data.frame(). Default is FALSE. #' @param verbose logical, if messages should be displayed. Default is FALSE. #' @param as_tibble logical, if the output should be a tibble (as opposed to a data.frame). Default is FALSE. #' @param .name_repair A string or function passed on as `.name_repair` to [tibble::as_tibble()] @@ -253,7 +241,7 @@ #' - `"universal"` : Checks names are unique and valid R variables names in scope #' - A function to apply custom name repair. #' -#' Default is `"check_unique"`. +#' Default is `"unique"`. #' #' @return A data frame (\code{data.frame}) containing a representation of data in the (f)ods file. #' @author Peter Brohan , Chung-hong Chan , Gerrit-Jan Schutten @@ -283,10 +271,9 @@ read_ods <- function(path, range = NULL, row_names = FALSE, strings_as_factors = FALSE, - check_names = FALSE, verbose = FALSE, - as_tibble = FALSE, - .name_repair = "check_unique" + as_tibble = TRUE, + .name_repair = "unique" ) { ## Should use match.call but there's a weird bug if one of the variable names is 'file' @@ -300,7 +287,6 @@ read_ods <- function(path, range, row_names, strings_as_factors, - check_names, verbose, as_tibble, .name_repair, @@ -319,10 +305,9 @@ read_fods <- function(path, range = NULL, row_names = FALSE, strings_as_factors = FALSE, - check_names = FALSE, verbose = FALSE, - as_tibble = FALSE, - .name_repair = "check_unique" + as_tibble = TRUE, + .name_repair = "unique" ) { ## Should use match.call but there's a weird bug if one of the variable names is 'file' @@ -336,7 +321,6 @@ read_fods <- function(path, range, row_names, strings_as_factors, - check_names, verbose, as_tibble, .name_repair, diff --git a/tests/testthat/test_as_tibble.R b/tests/testthat/test_as_tibble.R index aa14ce9..a824f55 100644 --- a/tests/testthat/test_as_tibble.R +++ b/tests/testthat/test_as_tibble.R @@ -3,18 +3,17 @@ test_that("as_tibble works", { x <- read_ods("../testdata/multisheet.ods", as_tibble = TRUE) expect_equal(tibble::is_tibble(x), TRUE) x <- read_ods("../testdata/multisheet.ods", sheet = 2, col_names = FALSE, as_tibble = TRUE) - expect_equal(x[[1, "A"]], "COOKIES") - expect_equal(x[[4, "B"]], 1) - expect_equal(typeof(x[[4, "B"]]), "double") + expect_equal(x[[1, "...1"]], "COOKIES") + expect_equal(x[[4, "...2"]], 1) + expect_equal(typeof(x[[4, "...2"]]), "double") }) -test_that(".name_repair words", { +test_that(".name_repair works", { expect_error(read_ods("../testdata/test_naming.ods", as_tibble = TRUE, - .name_repair = "error"), - ".name_repair must either") - expect_error(read_ods("../testdata/test_naming.ods", as_tibble = TRUE)) - expect_error(x <- read_ods("../testdata/test_naming.ods", as_tibble = TRUE, .name_repair = "unique"), NA) - expected_names <- c("a...1", "a...2", "Var.3") + .name_repair = "error")) + expect_error(read_ods("../testdata/test_naming.ods", as_tibble = TRUE, .name_repair = "check_unique")) + expect_error(x <- read_ods("../testdata/test_naming.ods", as_tibble = TRUE), NA) + expected_names <- c("a...1", "a...2", "...3") expect_equal(colnames(x), expected_names) }) \ No newline at end of file From e2fb44cc16bdfd0c44247dcb58d192a1dbaed3fc Mon Sep 17 00:00:00 2001 From: Peter <44036274+pbrohan@users.noreply.github.com> Date: Mon, 24 Jul 2023 20:50:00 +0100 Subject: [PATCH 2/7] fixed write tests --- NEWS.md | 3 ++- R/read_ods.R | 2 +- tests/testthat/test_cellranger.R | 8 +++--- tests/testthat/test_col_types.R | 16 +++++++++--- tests/testthat/test_issue81.R | 7 +++-- tests/testthat/test_legacy.R | 26 +++++++++---------- tests/testthat/test_merged.R | 16 ++++++------ tests/testthat/test_multiline.R | 2 +- tests/testthat/test_na.R | 42 +++++++++++++++--------------- tests/testthat/test_read_fods.R | 4 +-- tests/testthat/test_read_ods.R | 44 ++++++++++++++++---------------- 11 files changed, 92 insertions(+), 78 deletions(-) diff --git a/NEWS.md b/NEWS.md index 115cb98..124232c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,7 +17,8 @@ These have been deprecated for several years. * Changed behaviour when only one column is read. Previously gave an error. If row names are requested, gives a warning that this would cause the output to be empty, and does not assign names. * Sheets are now accepted as part of the `range` argument, e.g. `Range = "Sheet2!A2:B7"`. If this and the `sheets` argument are given, this is preferred. * Merged cells now have their value places in the top-left cell. All other cells that would be covered by the merge are filled with `NA`. -* Added `as_tibble` and `.name_repair` as arugments. If `as_tibble` is true, outputs as a tibble using `tibble::as_tibble()` passing on `.name_repair` (default being `"check_unique"`). +* Added `as_tibble` and `.name_repair` as arguments. If `as_tibble` is true, outputs as a tibble using `tibble::as_tibble()` passing on `.name_repair` (default being `"unique"`). **By default `as_tibble` is set to TRUE, and all +* Removed `check_names` argument. All name repairs are now dealt with using `vctrs::vec_as_names()`. This will **significantly change** the default names given to outputs ## read_fods diff --git a/R/read_ods.R b/R/read_ods.R index 25d3767..9d7e41e 100644 --- a/R/read_ods.R +++ b/R/read_ods.R @@ -233,7 +233,7 @@ #' @param row_names logical, indicating whether the file contains the names of the rows as its first column. Default is FALSE. #' @param strings_as_factors logical, if character columns to be converted to factors. Default is FALSE. #' @param verbose logical, if messages should be displayed. Default is FALSE. -#' @param as_tibble logical, if the output should be a tibble (as opposed to a data.frame). Default is FALSE. +#' @param as_tibble logical, if the output should be a tibble (as opposed to a data.frame). Default is TRUE. #' @param .name_repair A string or function passed on as `.name_repair` to [tibble::as_tibble()] #' - `"minimal"`: No name repair #' - `"unique"` : Make sure names are unique and not empty diff --git a/tests/testthat/test_cellranger.R b/tests/testthat/test_cellranger.R index 4772f5d..790bad2 100644 --- a/tests/testthat/test_cellranger.R +++ b/tests/testthat/test_cellranger.R @@ -2,13 +2,15 @@ test_that("Cell ranger related problem #57", { ods_3r <- read_ods('../testdata/cellranger_issue57.ods', sheet = 1, range = "A1:B4", col_names = TRUE) ods_2r <- read_ods('../testdata/cellranger_issue57.ods', sheet = 1, range = "A1:B3", col_names = TRUE) ods_a2_1r <- read_ods('../testdata/cellranger_issue57.ods', sheet = 1, range = "A2:B3", col_names = TRUE) + ods_a2_1r_df <- read_ods('../testdata/cellranger_issue57.ods', sheet = 1, range = "A2:B3", col_names = TRUE, as_tibble = FALSE) ods_a1_noheader_3r <- read_ods('../testdata/cellranger_issue57.ods', sheet = 1, range = "A1:B3", col_names = FALSE) expect_equal(nrow(ods_3r), 3) expect_equal(nrow(ods_2r), 2) expect_equal(nrow(ods_2r), 2) expect_equal(nrow(ods_a2_1r), 1) expect_equal(nrow(ods_a1_noheader_3r), 3) - expect_equal(ods_3r[3,1], 3) - expect_equal(ods_2r[2,1], 2) - expect_equal(colnames(ods_a2_1r)[1], "1") + expect_equal(ods_3r[[3,1]], 3) + expect_equal(ods_2r[[2,1]], 2) + expect_equal(colnames(ods_a2_1r)[1], "X1") + expect_equal(colnames(ods_a2_1r_df)[1], "X1") }) diff --git a/tests/testthat/test_col_types.R b/tests/testthat/test_col_types.R index c53d516..0e6391e 100644 --- a/tests/testthat/test_col_types.R +++ b/tests/testthat/test_col_types.R @@ -1,16 +1,24 @@ test_that("col_types ODS", { - x <- read_ods('../testdata/col_types.ods', col_types = NA) - expect_equal(class(x[,2]), "character") + x <- read_ods('../testdata/col_types.ods', col_types = NA, as_tibble = TRUE) + expect_equal(class(x[[2]]), "character") x <- read_ods('../testdata/col_types.ods') + expect_equal(class(x[[2]]), "numeric") + x <- read_ods('../testdata/col_types.ods', col_types = NA, as_tibble = FALSE) + expect_equal(class(x[,2]), "character") + x <- read_ods('../testdata/col_types.ods', as_tibble = FALSE) expect_equal(class(x[,2]), "numeric") }) ### test for issue #41 test_that("multi col_types ODS", { - x <- read_ods('../testdata/col_types.ods', col_types = NA) + x <- read_ods('../testdata/col_types.ods', col_types = NA, as_tibble = FALSE) expect_equal(class(x[,2]), "character") - x <- read_ods('../testdata/col_types.ods', col_types = readr::cols(cola = 'c', colb = 'c', colc = 'i')) + x <- read_ods('../testdata/col_types.ods', col_types = readr::cols(cola = 'c', colb = 'c', colc = 'i'), as_tibble = FALSE) expect_equal(class(x[,2]), "character") + x <- read_ods('../testdata/col_types.ods', col_types = NA, as_tibble = TRUE) + expect_equal(class(x[[2]]), "character") + x <- read_ods('../testdata/col_types.ods', col_types = readr::cols(cola = 'c', colb = 'c', colc = 'i'), as_tibble = TRUE) + expect_equal(class(x[[2]]), "character") }) ### throw an error if col_types is not col_spec, single value NA or single value NULL diff --git a/tests/testthat/test_issue81.R b/tests/testthat/test_issue81.R index cf0649c..c2127ae 100644 --- a/tests/testthat/test_issue81.R +++ b/tests/testthat/test_issue81.R @@ -2,14 +2,17 @@ ## excel_repeat.ods is created with MS Office 365 online test_that("issue 81, correctness", { - res <- read_ods("../testdata/excel_repeat.ods", col_names = FALSE) + res <- read_ods("../testdata/excel_repeat.ods", col_names = FALSE, as_tibble = FALSE) expect_identical(res[,1], c(rep("A", 12), rep("C", 11))) expect_identical(res[,2], c(rep("B", 12), rep("D", 11))) + res <- read_ods("../testdata/excel_repeat.ods", col_names = FALSE) + expect_identical(res[[1]], c(rep("A", 12), rep("C", 11))) + expect_identical(res[[2]], c(rep("B", 12), rep("D", 11))) }) test_that("issue 81 real test", { file <- "../testdata/issue81.ods" - res <- read_ods(file, sheet = 2, skip = 4) + res <- read_ods(file, sheet = 2, skip = 4, as_tibble = FALSE) expect_equal(sum(is.na(res[,1])), 0) }) diff --git a/tests/testthat/test_legacy.R b/tests/testthat/test_legacy.R index 1c4105d..5e34106 100644 --- a/tests/testthat/test_legacy.R +++ b/tests/testthat/test_legacy.R @@ -13,35 +13,35 @@ test_that("get_num_sheets_in_ods", { test_that("read_ods", { file <- "../testdata/sum.ods" - expect_equal(read_ods(file, sheet=1, col_names = FALSE, formula_as_formula=TRUE)[3,1],"of:=SUM([.A1:.A2])") - expect_equal(read_ods(file, sheet=1, col_names = FALSE, col_types = NA, formula_as_formula=FALSE)[3,1],"3") + expect_equal(read_ods(file, sheet=1, col_names = FALSE, formula_as_formula=TRUE)[[3,1]],"of:=SUM([.A1:.A2])") + expect_equal(read_ods(file, sheet=1, col_names = FALSE, col_types = NA, formula_as_formula=FALSE)[[3,1]],"3") - df <- data.frame(A = as.character(1:3),stringsAsFactors = F) - rODS <- read_ods(file, sheet = 1, col_names = FALSE, col_types=NA, formula_as_formula = FALSE) + df <- data.frame("...1" = as.character(1:3),stringsAsFactors = F) + rODS <- read_ods(file, sheet = 1, col_names = FALSE, col_types=NA, formula_as_formula = FALSE, as_tibble = FALSE) expect_equal(rODS, df) file <- "../testdata/lotsofnothing_test.ods" expect_equal(dim(read_ods(file, sheet = 1, col_names = FALSE)),c(21,13)) # test if empty rows at the end are ignored - expect_equal(class(read_ods(file, sheet=1)), "data.frame") + expect_equal(inherits(read_ods(file, sheet = 1), "tbl_df"), TRUE) ## small file file <- "../testdata/table.ods" - df <- data.frame(A = c("gender", "m", "f", "m"), - B = c("visit1", "4", "8", "8"), - C = c("visit2", "6", "9", "2"), - D = c("visit3", "8", "4", "1"), + df <- data.frame("...1" = c("gender", "m", "f", "m"), + "...2" = c("visit1", "4", "8", "8"), + "...3" = c("visit2", "6", "9", "2"), + "...4" = c("visit3", "8", "4", "1"), stringsAsFactors = F) - expect_equal(read_ods(file, sheet = 1, col_names = FALSE, col_types = NA), df) + expect_equal(read_ods(file, sheet = 1, col_names = FALSE, col_types = NA, as_tibble = FALSE), df) file <- "../testdata/layout_test.ods" sheet1 <- read_ods(file, sheet = 1, col_names = FALSE) - expect_equal(sheet1[8, "F"], "empty") # this is a repeated element + expect_equal(sheet1[[8, "...6"]], "empty") # this is a repeated element sheet2 <- read_ods(file, sheet=2, col_names = FALSE) expect_equal(dim(sheet2),c(22,13)) expect_true(all(sheet1[21,]==sheet2[22,])) file <- paste("../testdata/multisheet.ods",sep="") - df <- data.frame(matrix(as.character(NA),14,7),stringsAsFactors = F) + df <- data.frame(matrix(as.character(NA),14,7), stringsAsFactors = F) df[1,1] <- "COOKIES" df[4,2] <- "1" df[6,3] <- "2" @@ -50,7 +50,7 @@ test_that("read_ods", { df[7,5] <- "3" df[9,5] <- "1" df[10,7] <- "1" - sheet2 <- read_ods(file, sheet=2, col_names = FALSE) + sheet2 <- read_ods(file, sheet=2, col_names = FALSE, as_tibble = FALSE) expect_true(all(sheet2==df, na.rm = TRUE)) file <- "../testdata/1996-2000.ods" diff --git a/tests/testthat/test_merged.R b/tests/testthat/test_merged.R index 2ac498a..7aa1bcb 100644 --- a/tests/testthat/test_merged.R +++ b/tests/testthat/test_merged.R @@ -1,11 +1,11 @@ test_that("merged cells work", { - merged <- read_ods("../testdata/merged.ods") - expect_equal(merged[4, 1], 4) - expect_true(is.na(merged[4, 2])) - expect_equal(merged[4, 3], "d") - expect_equal(merged[5, 1], 5) + merged <- read_ods("../testdata/merged.ods", as_tibble = TRUE) + expect_equal(merged[[4, 1]], 4) + expect_true(is.na(merged[[4, 2]])) + expect_equal(merged[[4, 3]], "d") + expect_equal(merged[[5, 1]], 5) expect_true(is.na(merged[6, 1])) - expect_equal(merged[7, 1], 7) - expect_true(is.na(merged[8, 2])) - expect_equal(merged[9,2], "hidden_text") + expect_equal(merged[[7, 1]], 7) + expect_true(is.na(merged[[8, 2]])) + expect_equal(merged[[9,2]], "hidden_text") }) diff --git a/tests/testthat/test_multiline.R b/tests/testthat/test_multiline.R index 97649bc..bbc8322 100644 --- a/tests/testthat/test_multiline.R +++ b/tests/testthat/test_multiline.R @@ -1,4 +1,4 @@ test_that("multiline values", { x <- read_ods('../testdata/multiline_cells.ods', col_names = FALSE) - expect_equal(x[1, 1], "Multiline cell, line 1\nMultiline cell, line 2") + expect_equal(x[[1, 1]], "Multiline cell, line 1\nMultiline cell, line 2") }) diff --git a/tests/testthat/test_na.R b/tests/testthat/test_na.R index 9671f25..f1f95f4 100644 --- a/tests/testthat/test_na.R +++ b/tests/testthat/test_na.R @@ -1,34 +1,34 @@ test_that("expected na behaviour", { na_res <- read_ods('../testdata/na_test.ods', na = c("3", "999", "missing")) - expect_true(is.na(na_res[4,1])) - expect_true(is.na(na_res[4,2])) - expect_true(is.na(na_res[3,3])) - expect_true(is.na(na_res[4,3])) + expect_true(is.na(na_res[[4, 1]])) + expect_true(is.na(na_res[[4, 2]])) + expect_true(is.na(na_res[[3, 3]])) + expect_true(is.na(na_res[[4, 3]])) }) test_that("type_convert honoring na #1, issue 78", { na_res <- read_ods('../testdata/na_test2.ods', na = c("one")) - expect_true(is.na(na_res[1,1])) - expect_false(is.na(na_res[2,1])) - expect_false(is.na(na_res[3,1])) - expect_false(is.na(na_res[4,1])) - expect_false(is.na(na_res[5,1])) - expect_false(is.na(na_res[6,1])) - expect_false(is.na(na_res[7,1])) - expect_false(is.na(na_res[8,1])) - expect_false(is.na(na_res[9,1])) - expect_false(is.na(na_res[10,1])) - expect_equal((na_res[2,1]), "NaN") - expect_equal((na_res[4,1]), "") - expect_equal((na_res[6,1]), "NA") - expect_equal((na_res[7,1]), "NA") - expect_equal((na_res[8,1]), "") + expect_true(is.na(na_res[[1,1]])) + expect_false(is.na(na_res[[2,1]])) + expect_false(is.na(na_res[[3,1]])) + expect_false(is.na(na_res[[4,1]])) + expect_false(is.na(na_res[[5,1]])) + expect_false(is.na(na_res[[6,1]])) + expect_false(is.na(na_res[[7,1]])) + expect_false(is.na(na_res[[8,1]])) + expect_false(is.na(na_res[[9,1]])) + expect_false(is.na(na_res[[10,1]])) + expect_equal((na_res[[2,1]]), "NaN") + expect_equal((na_res[[4,1]]), "") + expect_equal((na_res[[6,1]]), "NA") + expect_equal((na_res[[7,1]]), "NA") + expect_equal((na_res[[8,1]]), "") }) test_that("type_convert honoring na #2, issue 78", { na_res <- read_ods('../testdata/na_test3.ods', na = c("one")) - expect_true(is.na(na_res[1,1])) - expect_false(is.na(na_res[2,1])) + expect_true(is.na(na_res[[1,1]])) + expect_false(is.na(na_res[[2,1]])) expect_false(any(is.na(na_res$ex1[2:9]))) expect_false(any(is.na(na_res$ex2[1:9]))) expect_true(any(is.na(na_res$ex3[1:9]))) ## due to col_types diff --git a/tests/testthat/test_read_fods.R b/tests/testthat/test_read_fods.R index 4077ac5..dd90120 100644 --- a/tests/testthat/test_read_fods.R +++ b/tests/testthat/test_read_fods.R @@ -1,8 +1,8 @@ test_that("Read fods", { expect_silent(a <- read_fods("../testdata/flat.fods")) - expect_equal(a[1,1], "A2") + expect_equal(a[[1,1]], "A2") b <- read_fods("../testdata/flat.fods", range = "Sheet2!B2:D3") - expect_equal(b[1,3], "S2D3") + expect_equal(b[[1,3]], "S2D3") }) test_that("Error when not correct", { diff --git a/tests/testthat/test_read_ods.R b/tests/testthat/test_read_ods.R index 6288234..32ae2c3 100644 --- a/tests/testthat/test_read_ods.R +++ b/tests/testthat/test_read_ods.R @@ -7,21 +7,20 @@ test_that("Incorrect Argument", { expect_error(read_ods(path = "../testdata/sum.ods", formula_as_formula = "a"), "formula_as_formula must be of type `boolean`") expect_error(read_ods(path = "../testdata/sum.ods", row_names = "a"), "row_names must be of type `boolean`") expect_error(read_ods(path = "../testdata/sum.ods", strings_as_factors = "a"), "strings_as_factors must be of type `boolean`") - expect_error(read_ods(path = "../testdata/sum.ods", check_names = "a"), "check_names must be of type `boolean`") expect_error(read_ods(path = "../testdata/sum.ods", verbose = "a"), "verbose must be of type `boolean`") }) test_that("Single column ODS", { single_col <- read_ods('../testdata/sum.ods', sheet = 1) expect_equal(ncol(single_col),1) - expect_equal(colnames(single_col), c("1")) + expect_equal(colnames(single_col), c("X1")) expect_warning(read_ods('../testdata/sum.ods', sheet = 1, row_names = TRUE), "Cannot make") }) test_that("Single row ODS", { expect_warning(single_row <- read_ods('../testdata/onerow.ods', sheet = 1), "Cannot make") expect_equal(nrow(single_row), 1) - expect_equal(single_row[1,1], 1) + expect_equal(single_row[[1,1]], 1) }) test_that("Single column range", { @@ -34,7 +33,7 @@ test_that("read_ods works with all kind of character encodings", { }) test_that("read_ods reads decimals properly with comma", { - df <- read_ods('../testdata/decimal_comma.ods') + df <- read_ods('../testdata/decimal_comma.ods', as_tibble = FALSE) df_expected <- structure(list(A = 3.4, B = 2.3, C = 0.03), .Names = c("A", "B", "C"), row.names = 1L, class = "data.frame") @@ -43,45 +42,46 @@ test_that("read_ods reads decimals properly with comma", { test_that("eating space issue #74", { df <- read_ods("../testdata/eating_spaces.ods", sheet = 2, col_names = FALSE) - expect_equal(df[1,1], "A B") + expect_equal(df[[1,1]], "A B") df <- read_ods("../testdata/eating_spaces.ods", sheet = 3, col_names = FALSE) - expect_equal(df[1,1], "A B C") + expect_equal(df[[1,1]], "A B C") df <- read_ods("../testdata/eating_spaces.ods", sheet = 4, col_names = FALSE) - expect_equal(df[1,1], "A B C") + expect_equal(df[[1,1]], "A B C") df <- read_ods("../testdata/eating_spaces.ods", sheet = 5, col_names = FALSE) - expect_equal(df[1,1], "A B\nC") + expect_equal(df[[1,1]], "A B\nC") }) test_that("skip", { expect_silent(x <- read_ods("../testdata/starwars.ods", skip = 0)) expect_equal(nrow(x), 10) - expect_silent(x <- read_ods("../testdata/starwars.ods", skip = 1, col_names = FALSE)) + expect_message(x <- read_ods("../testdata/starwars.ods", skip = 1, col_names = FALSE)) expect_equal(nrow(x), 10) expect_warning(x <- read_ods("../testdata/starwars.ods", skip = 11), "empty sheet") expect_equal(nrow(x), 0) }) -test_that("Check names works properly", { - expect_silent(x <- read_ods("../testdata/test_naming.ods")) - expect_equal(colnames(x), c("a", "a", "Var.3")) - expect_silent(x <- read_ods("../testdata/test_naming.ods", check_names = TRUE)) +test_that("Check .name_repair works properly", { + expect_silent(x <- read_ods("../testdata/test_naming.ods", .name_repair = "minimal")) expect_equal(colnames(x), c("a", "a.1", "Var.3")) + expect_error(x <- read_ods("../testdata/test_naming.ods", .name_repair = "check_unique")) + expect_message(x <- read_ods("../testdata/test_naming.ods", .name_repair = "unique")) + expect_equal(colnames(x), c("a...1", "a...2", "...3")) }) test_that("Parses range inputs correctly", { expect_warning(x <- read_ods("../testdata/multisheet.ods", sheet = 3, range = "Sheet2!B4:D9"), "Sheet suggested in range and using sheet") - expect_equal(x[2,2], 2) - expect_silent(x <- read_ods("../testdata/multisheet.ods", range = "Sheet3!D2:E4")) - expect_equal(x[1,1], 3) + expect_equal(x[[2,2]], 2) + expect_message(x <- read_ods("../testdata/multisheet.ods", range = "Sheet3!D2:E4")) + expect_equal(x[[1,1]], 3) }) test_that("Deals with repeated spaces correctly when fetching only part of sheet",{ - df <- data.frame(A = c(1, NA, NA, NA), - B = c(NA, NA, 2, NA), - C = c(NA, NA, NA, NA), - D = c(NA, NA, NA, 3)) - expect_equal(read_ods("../testdata/multisheet.ods", range = "Sheet2!B4:E7", col_names = FALSE), df) - expect_equal(read_ods("../testdata/excel_repeat.ods", range = "A9:B18", col_names = FALSE)[5,1], "C") + df <- data.frame("...1" = c(1, NA, NA, NA), + "...2" = c(NA, NA, 2, NA), + "...3" = c(NA, NA, NA, NA), + "...4" = c(NA, NA, NA, 3)) + expect_equal(read_ods("../testdata/multisheet.ods", range = "Sheet2!B4:E7", col_names = FALSE, as_tibble = FALSE), df) + expect_equal(read_ods("../testdata/excel_repeat.ods", range = "A9:B18", col_names = FALSE)[[5,1]], "C") }) test_that("Warns of empty sheet", { From c1a9042634a7113b0265abdd7b292f2128fe9a48 Mon Sep 17 00:00:00 2001 From: Peter <44036274+pbrohan@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:13:08 +0100 Subject: [PATCH 3/7] Updated remaning tests and documentation --- NEWS.md | 7 +- R/read_ods.R | 11 +- R/writeODS.R | 7 +- README.Rmd | 4 +- README.md | 178 ++++++++---------- man/read_ods.Rd | 16 +- man/write_ods.Rd | 2 +- tests/testthat/test_read_ods.R | 12 +- tests/testthat/test_write_ods_append_update.R | 23 ++- vignettes/overview.Rmd | 5 - 10 files changed, 131 insertions(+), 134 deletions(-) diff --git a/NEWS.md b/NEWS.md index e8f0751..5c341e1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -27,14 +27,17 @@ These have been deprecated for several years. * Added `include_external_data` as an argument (`FALSE` by default). This hides stored data from external sources not normally accessible to the user. +## BREAKING CHANGES: read_ods now outputs as tibble by default +* Added `as_tibble` and `.name_repair` as arguments. If `as_tibble` is true, outputs as a tibble using `tibble::as_tibble()` passing on `.name_repair` (default being `"unique"`). **By default** `as_tibble` is set to TRUE. +* Removed `check_names` argument. All name repairs are now dealt with using `vctrs::vec_as_names()`. This will **significantly change** the default names given to outputs. (Names in the style of `check_names = TRUE` can be obtained by setting `.name_repair = minimal`, although this is not advised) + ## read_ods * Changed behaviour when only one row is read. The row now correctly appears as the top row in the dataframe, and a warning is given if column headers are requested that this would cause the output to be empty (**Note:** in this case column names are not assigned) * Changed behaviour when only one column is read. Previously gave an error. If row names are requested, gives a warning that this would cause the output to be empty, and does not assign names. * Sheets are now accepted as part of the `range` argument, e.g. `Range = "Sheet2!A2:B7"`. If this and the `sheets` argument are given, this is preferred. * Merged cells now have their value places in the top-left cell. All other cells that would be covered by the merge are filled with `NA`. -* Added `as_tibble` and `.name_repair` as arguments. If `as_tibble` is true, outputs as a tibble using `tibble::as_tibble()` passing on `.name_repair` (default being `"unique"`). **By default `as_tibble` is set to TRUE, and all -* Removed `check_names` argument. All name repairs are now dealt with using `vctrs::vec_as_names()`. This will **significantly change** the default names given to outputs + ## read_fods diff --git a/R/read_ods.R b/R/read_ods.R index 9d7e41e..223aee2 100644 --- a/R/read_ods.R +++ b/R/read_ods.R @@ -10,7 +10,11 @@ rownames(g) <- if(row_header) x[seq(irow, nrow(x)), 1] else NULL # don't want character row headers given by 1:nrow(g) - col_n <- if(col_header) x[1, seq(jcol, ncol(x))] else c(rep("", ncol(x))) + cols <- ncol(x) + if (row_header) { + cols <- cols - 1 + } + col_n <- if(col_header) x[1, seq(jcol, ncol(x))] else c(rep("", cols)) colnames(g) <- vctrs::vec_as_names(unlist(col_n), repair = .name_repair) return(g) } @@ -99,8 +103,9 @@ if (!is.logical(as_tibble)) { stop("as_tibble must be of type `boolean", call. = FALSE) } - else - {} + if (row_names && as_tibble){ + stop("Tibbles do not support row names. To use row names, set as_tibble to false", call. = FALSE) + } } .read_ods <- function(path, diff --git a/R/writeODS.R b/R/writeODS.R index 935379f..8630570 100644 --- a/R/writeODS.R +++ b/R/writeODS.R @@ -100,7 +100,7 @@ } ## https://github.com/ropensci/readODS/issues/88 -.vfwrite_ods <- function(x, temp_ods_dir, sheet = "Sheet1", row_names = FALSE, col_names = FALSE, na_as_string = FALSE) { +.vfwrite_ods <- function(x, temp_ods_dir, sheet = "Sheet1", row_names = FALSE, col_names = TRUE, na_as_string = FALSE) { templatedir <- system.file("template", package = "readODS") file.copy(dir(templatedir, full.names = TRUE), temp_ods_dir, recursive = TRUE, copy.mode = FALSE) con <- file(file.path(temp_ods_dir, "content.xml"), open="w", encoding = "UTF-8") @@ -121,7 +121,7 @@ #' @param append logical, TRUE indicates that x should be appended to the existing file (path) as a new sheet. If a sheet with the same sheet_name exists, an exception is thrown. See update. Please also note that writing is slower if TRUE. Default is FALSE. #' @param update logical, TRUE indicates that the sheet with sheet_name in the existing file (path) should be updated with the content of x. If a sheet with sheet_name does not exist, an exception is thrown. Please also note that writing is slower if TRUE. Default is FALSE. #' @param row_names logical, TRUE indicates that row names of x are to be included in the sheet. Default is FALSE. -#' @param col_names logical, TRUE indicates that column names of x are to be included in the sheet. Default is FALSE. +#' @param col_names logical, TRUE indicates that column names of x are to be included in the sheet. Default is TRUE. #' @param na_as_string logical, TRUE indicates that NAs are written as string. #' @return An ODS file written to the file path location specified by the user. The value of \code{path} is also returned invisibly. #' @author Detlef Steuer , Thomas J. Leeper , John Foster , Chung-hong Chan @@ -139,6 +139,9 @@ write_ods <- function(x, path, sheet = "Sheet1", append = FALSE, update = FALSE, temp_ods_dir <- file.path(tempdir(), stringi::stri_rand_strings(1, 20, pattern = "[A-Za-z0-9]")) dir.create(temp_ods_dir) on.exit(unlink(temp_ods_dir)) + if (inherits(x, "tbl_df")){ #Convert to a df if currently a tibble + x <- as.data.frame(x) + } if (!is.data.frame(x)) { stop("x must be a data.frame.", call. = FALSE) } diff --git a/README.Rmd b/README.Rmd index d4824b6..f65ddd0 100644 --- a/README.Rmd +++ b/README.Rmd @@ -68,9 +68,9 @@ Reading from a specific range read_ods("starwars.ods", sheet = 2, range = "A1:C11") ``` -Reading as a tibble +Reading as a dataframe ```{r} -read_ods("starwars.ods", range="Sheet1!A2:C11", as_tibble = TRUE) +read_ods("starwars.ods", range="Sheet1!A2:C11", as_tibble = FALSE) ``` #### Writing diff --git a/README.md b/README.md index c3d3c27..80521fa 100644 --- a/README.md +++ b/README.md @@ -48,80 +48,73 @@ In almost all use cases, you only need two functions: `read_ods` and ``` r library(readODS) read_ods("starwars.ods") -#> Name homeworld species -#> 1 Luke Skywalker Tatooine Human -#> 2 C-3PO Tatooine Human -#> 3 R2-D2 Alderaan Human -#> 4 Darth Vader Tatooine Human -#> 5 Leia Organa Tatooine Human -#> 6 Owen Lars Tatooine Human -#> 7 Beru Whitesun lars Stewjon Human -#> 8 R5-D4 Tatooine Human -#> 9 Biggs Darklighter Kashyyyk Wookiee -#> 10 Obi-Wan Kenobi Corellia Human +#> # A tibble: 10 × 3 +#> Name homeworld species +#> +#> 1 Luke Skywalker Tatooine Human +#> 2 C-3PO Tatooine Human +#> 3 R2-D2 Alderaan Human +#> 4 Darth Vader Tatooine Human +#> 5 Leia Organa Tatooine Human +#> 6 Owen Lars Tatooine Human +#> 7 Beru Whitesun lars Stewjon Human +#> 8 R5-D4 Tatooine Human +#> 9 Biggs Darklighter Kashyyyk Wookiee +#> 10 Obi-Wan Kenobi Corellia Human ``` Reading from the 2nd sheet ``` r read_ods("starwars.ods", sheet = 2) -#> Name height mass hair_color skin_color eye_color birth_year -#> 1 Luke Skywalker 172 77 blond fair blue 19.0 -#> 2 C-3PO 202 136 none white yellow 41.9 -#> 3 R2-D2 150 49 brown light brown 19.0 -#> 4 Darth Vader 178 120 brown, grey light blue 52.0 -#> 5 Leia Organa 165 75 brown light blue 47.0 -#> 6 Owen Lars 183 84 black light brown 24.0 -#> 7 Beru Whitesun lars 182 77 auburn, white fair blue-gray 57.0 -#> 8 R5-D4 188 84 blond fair blue 41.9 -#> 9 Biggs Darklighter 228 112 brown unknown blue 200.0 -#> 10 Obi-Wan Kenobi 180 80 brown fair brown 29.0 -#> gender -#> 1 male -#> 2 male -#> 3 female -#> 4 male -#> 5 female -#> 6 male -#> 7 male -#> 8 male -#> 9 male -#> 10 male +#> # A tibble: 10 × 8 +#> Name height mass hair_color skin_color eye_color birth_year gender +#> +#> 1 Luke Skywalker 172 77 blond fair blue 19 male +#> 2 C-3PO 202 136 none white yellow 41.9 male +#> 3 R2-D2 150 49 brown light brown 19 female +#> 4 Darth Vader 178 120 brown, gr… light blue 52 male +#> 5 Leia Organa 165 75 brown light blue 47 female +#> 6 Owen Lars 183 84 black light brown 24 male +#> 7 Beru Whitesun… 182 77 auburn, w… fair blue-gray 57 male +#> 8 R5-D4 188 84 blond fair blue 41.9 male +#> 9 Biggs Darklig… 228 112 brown unknown blue 200 male +#> 10 Obi-Wan Kenobi 180 80 brown fair brown 29 male ``` Reading from a specific range ``` r read_ods("starwars.ods", sheet = 2, range = "A1:C11") -#> Name height mass -#> 1 Luke Skywalker 172 77 -#> 2 C-3PO 202 136 -#> 3 R2-D2 150 49 -#> 4 Darth Vader 178 120 -#> 5 Leia Organa 165 75 -#> 6 Owen Lars 183 84 -#> 7 Beru Whitesun lars 182 77 -#> 8 R5-D4 188 84 -#> 9 Biggs Darklighter 228 112 -#> 10 Obi-Wan Kenobi 180 80 +#> # A tibble: 10 × 3 +#> Name height mass +#> +#> 1 Luke Skywalker 172 77 +#> 2 C-3PO 202 136 +#> 3 R2-D2 150 49 +#> 4 Darth Vader 178 120 +#> 5 Leia Organa 165 75 +#> 6 Owen Lars 183 84 +#> 7 Beru Whitesun lars 182 77 +#> 8 R5-D4 188 84 +#> 9 Biggs Darklighter 228 112 +#> 10 Obi-Wan Kenobi 180 80 ``` -Reading as a tibble +Reading as a dataframe ``` r -read_ods("starwars.ods", range="Sheet1!A2:C11", as_tibble = TRUE) -#> # A tibble: 9 × 3 -#> `Luke Skywalker` Tatooine Human -#> -#> 1 C-3PO Tatooine Human -#> 2 R2-D2 Alderaan Human -#> 3 Darth Vader Tatooine Human -#> 4 Leia Organa Tatooine Human -#> 5 Owen Lars Tatooine Human -#> 6 Beru Whitesun lars Stewjon Human -#> 7 R5-D4 Tatooine Human -#> 8 Biggs Darklighter Kashyyyk Wookiee -#> 9 Obi-Wan Kenobi Corellia Human +read_ods("starwars.ods", range="Sheet1!A2:C11", as_tibble = FALSE) +#> Luke.Skywalker Tatooine Human +#> 1 C-3PO Tatooine Human +#> 2 R2-D2 Alderaan Human +#> 3 Darth Vader Tatooine Human +#> 4 Leia Organa Tatooine Human +#> 5 Owen Lars Tatooine Human +#> 6 Beru Whitesun lars Stewjon Human +#> 7 R5-D4 Tatooine Human +#> 8 Biggs Darklighter Kashyyyk Wookiee +#> 9 Obi-Wan Kenobi Corellia Human ``` #### Writing @@ -140,53 +133,38 @@ write_ods(PlantGrowth, "mtcars.ods", append = TRUE, sheet = "plant") ``` r ## Default: First sheet read_ods("mtcars.ods") -#> Var.1 mpg cyl disp hp drat wt qsec vs am gear carb -#> 1 Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4 -#> 2 Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4 -#> 3 Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1 -#> 4 Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1 -#> 5 Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2 -#> 6 Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1 -#> 7 Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4 -#> 8 Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2 -#> 9 Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2 -#> 10 Merc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4 -#> 11 Merc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4 -#> 12 Merc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3 -#> 13 Merc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3 -#> 14 Merc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3 -#> 15 Cadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4 -#> 16 Lincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4 -#> 17 Chrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4 -#> 18 Fiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1 -#> 19 Honda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2 -#> 20 Toyota Corolla 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1 -#> 21 Toyota Corona 21.5 4 120.1 97 3.70 2.465 20.01 1 0 3 1 -#> 22 Dodge Challenger 15.5 8 318.0 150 2.76 3.520 16.87 0 0 3 2 -#> 23 AMC Javelin 15.2 8 304.0 150 3.15 3.435 17.30 0 0 3 2 -#> 24 Camaro Z28 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4 -#> 25 Pontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2 -#> 26 Fiat X1-9 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1 -#> 27 Porsche 914-2 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2 -#> 28 Lotus Europa 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2 -#> 29 Ford Pantera L 15.8 8 351.0 264 4.22 3.170 14.50 0 1 5 4 -#> 30 Ferrari Dino 19.7 6 145.0 175 3.62 2.770 15.50 0 1 5 6 -#> 31 Maserati Bora 15.0 8 301.0 335 3.54 3.570 14.60 0 1 5 8 -#> 32 Volvo 142E 21.4 4 121.0 109 4.11 2.780 18.60 1 1 4 2 +#> New names: +#> • `` -> `...1` +#> # A tibble: 32 × 12 +#> ...1 mpg cyl disp hp drat wt qsec vs am gear carb +#> +#> 1 Mazda RX4 21 6 160 110 3.9 2.62 16.5 0 1 4 4 +#> 2 Mazda RX4 … 21 6 160 110 3.9 2.88 17.0 0 1 4 4 +#> 3 Datsun 710 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1 +#> 4 Hornet 4 D… 21.4 6 258 110 3.08 3.22 19.4 1 0 3 1 +#> 5 Hornet Spo… 18.7 8 360 175 3.15 3.44 17.0 0 0 3 2 +#> 6 Valiant 18.1 6 225 105 2.76 3.46 20.2 1 0 3 1 +#> 7 Duster 360 14.3 8 360 245 3.21 3.57 15.8 0 0 3 4 +#> 8 Merc 240D 24.4 4 147. 62 3.69 3.19 20 1 0 4 2 +#> 9 Merc 230 22.8 4 141. 95 3.92 3.15 22.9 1 0 4 2 +#> 10 Merc 280 19.2 6 168. 123 3.92 3.44 18.3 1 0 4 4 +#> # ℹ 22 more rows ``` ``` r read_ods("mtcars.ods", sheet = "plant", range = "A1:B10") +#> # A tibble: 9 × 2 #> weight group -#> 1 4.17 ctrl -#> 2 5.58 ctrl -#> 3 5.18 ctrl -#> 4 6.11 ctrl -#> 5 4.50 ctrl -#> 6 4.61 ctrl -#> 7 5.17 ctrl -#> 8 4.53 ctrl -#> 9 5.33 ctrl +#> +#> 1 4.17 ctrl +#> 2 5.58 ctrl +#> 3 5.18 ctrl +#> 4 6.11 ctrl +#> 5 4.5 ctrl +#> 6 4.61 ctrl +#> 7 5.17 ctrl +#> 8 4.53 ctrl +#> 9 5.33 ctrl ``` ### Text Encoding diff --git a/man/read_ods.Rd b/man/read_ods.Rd index 82ccd1a..1ce8103 100644 --- a/man/read_ods.Rd +++ b/man/read_ods.Rd @@ -16,10 +16,9 @@ read_ods( range = NULL, row_names = FALSE, strings_as_factors = FALSE, - check_names = FALSE, verbose = FALSE, - as_tibble = FALSE, - .name_repair = "check_unique" + as_tibble = TRUE, + .name_repair = "unique" ) read_fods( @@ -33,10 +32,9 @@ read_fods( range = NULL, row_names = FALSE, strings_as_factors = FALSE, - check_names = FALSE, verbose = FALSE, - as_tibble = FALSE, - .name_repair = "check_unique" + as_tibble = TRUE, + .name_repair = "unique" ) } \arguments{ @@ -61,11 +59,9 @@ NULL, so that empty cells are treated as NA.} \item{strings_as_factors}{logical, if character columns to be converted to factors. Default is FALSE.} -\item{check_names}{logical, passed down to base::data.frame(). Default is FALSE.} - \item{verbose}{logical, if messages should be displayed. Default is FALSE.} -\item{as_tibble}{logical, if the output should be a tibble (as opposed to a data.frame). Default is FALSE.} +\item{as_tibble}{logical, if the output should be a tibble (as opposed to a data.frame). Default is TRUE.} \item{.name_repair}{A string or function passed on as \code{.name_repair} to \code{\link[tibble:as_tibble]{tibble::as_tibble()}} \itemize{ @@ -76,7 +72,7 @@ NULL, so that empty cells are treated as NA.} \item A function to apply custom name repair. } -Default is \code{"check_unique"}.} +Default is \code{"unique"}.} } \value{ A data frame (\code{data.frame}) containing a representation of data in the (f)ods file. diff --git a/man/write_ods.Rd b/man/write_ods.Rd index 70c0744..104648d 100644 --- a/man/write_ods.Rd +++ b/man/write_ods.Rd @@ -28,7 +28,7 @@ write_ods( \item{row_names}{logical, TRUE indicates that row names of x are to be included in the sheet. Default is FALSE.} -\item{col_names}{logical, TRUE indicates that column names of x are to be included in the sheet. Default is FALSE.} +\item{col_names}{logical, TRUE indicates that column names of x are to be included in the sheet. Default is TRUE.} \item{na_as_string}{logical, TRUE indicates that NAs are written as string.} } diff --git a/tests/testthat/test_read_ods.R b/tests/testthat/test_read_ods.R index 32ae2c3..38a3bf2 100644 --- a/tests/testthat/test_read_ods.R +++ b/tests/testthat/test_read_ods.R @@ -8,13 +8,14 @@ test_that("Incorrect Argument", { expect_error(read_ods(path = "../testdata/sum.ods", row_names = "a"), "row_names must be of type `boolean`") expect_error(read_ods(path = "../testdata/sum.ods", strings_as_factors = "a"), "strings_as_factors must be of type `boolean`") expect_error(read_ods(path = "../testdata/sum.ods", verbose = "a"), "verbose must be of type `boolean`") + expect_error(read_ods(path = "../testdata/sum.ods", row_names = TRUE), "Tibbles do not support") }) test_that("Single column ODS", { single_col <- read_ods('../testdata/sum.ods', sheet = 1) expect_equal(ncol(single_col),1) expect_equal(colnames(single_col), c("X1")) - expect_warning(read_ods('../testdata/sum.ods', sheet = 1, row_names = TRUE), "Cannot make") + expect_warning(read_ods('../testdata/sum.ods', sheet = 1, row_names = TRUE, as_tibble = FALSE), "Cannot make") }) test_that("Single row ODS", { @@ -87,4 +88,13 @@ test_that("Deals with repeated spaces correctly when fetching only part of sheet test_that("Warns of empty sheet", { expect_warning(read_ods("../testdata/empty.ods")) expect_warning(read_fods("../testdata/empty.fods")) +}) + +test_that("read with column headers", { + expect_silent(x <- read_ods("../testdata/starwars.ods", col_names = TRUE)) + expect_equal(ncol(x), 3) + expect_equal(nrow(x), 10) + expect_silent(x <- read_ods("../testdata/starwars.ods", row_names = TRUE, as_tibble = FALSE)) + expect_equal(ncol(x), 2) + expect_equal(colnames(x), c("homeworld", "species")) }) \ No newline at end of file diff --git a/tests/testthat/test_write_ods_append_update.R b/tests/testthat/test_write_ods_append_update.R index 9983560..06a51bc 100644 --- a/tests/testthat/test_write_ods_append_update.R +++ b/tests/testthat/test_write_ods_append_update.R @@ -39,36 +39,36 @@ test_that("Write Excel sheets", { expect_error(write_ods(starwars10, tmp, "SWRC", row_names=TRUE, col_names = TRUE, update = TRUE), NA) expect_error(write_ods(starwars10, tmp, "whatevernotexists", row_names=TRUE, col_names = TRUE, update = TRUE)) - df <- read_ods(tmp, "SW", row_names = FALSE, col_names = FALSE, strings_as_factors = TRUE) + df <- read_ods(tmp, "SW", row_names = FALSE, col_names = FALSE, strings_as_factors = TRUE, as_tibble = FALSE) expect_true(all.equal({ cars <- starwars10 rownames(cars) <- NULL - colnames(cars) <- cols_to_letters(ncol(cars)) + colnames(cars) <- vctrs::vec_as_names(rep("", 9), repair = "unique") cars }, df)) - df <- read_ods(tmp, "SWR", row_names = TRUE, col_names = FALSE, strings_as_factors = TRUE) + df <- read_ods(tmp, "SWR", row_names = TRUE, col_names = FALSE, strings_as_factors = TRUE, as_tibble = FALSE) expect_true(all.equal({ cars <- starwars10 - colnames(cars) <- cols_to_letters(ncol(cars)) + colnames(cars) <- vctrs::vec_as_names(rep("", 9), repair = "unique") cars}, df)) - df <- read_ods(tmp, "SWC", row_names = FALSE, col_names = TRUE, strings_as_factors = TRUE) + df <- read_ods(tmp, "SWC", row_names = FALSE, col_names = TRUE, strings_as_factors = TRUE, as_tibble = FALSE) expect_true(all.equal({ cars <- starwars10 rownames(cars) <- NULL cars }, df)) - df <- read_ods(tmp, "SWRC", row_names = TRUE, col_names = TRUE, strings_as_factors = TRUE) + df <- read_ods(tmp, "SWRC", row_names = TRUE, col_names = TRUE, strings_as_factors = TRUE, as_tibble = FALSE) expect_true(all.equal(starwars10, df)) - df <- read_ods(tmp, "SW1", row_names = TRUE, col_names = TRUE, strings_as_factors = TRUE) + df <- read_ods(tmp, "SW1", row_names = TRUE, col_names = TRUE, strings_as_factors = TRUE, as_tibble = FALSE) expect_false(isTRUE(all.equal(starwars10[1, seq_len(ncol(starwars10))], df))) # factor mismatch expect_true(all((df == starwars10[1, seq_len(ncol(starwars10))])[1,])) - df <- read_ods(tmp, "SW10", row_names = TRUE, col_names = TRUE, strings_as_factors = TRUE) + df <- read_ods(tmp, "SW10", row_names = TRUE, col_names = TRUE, strings_as_factors = TRUE, as_tibble = FALSE) expect_true(all.equal(starwars10[seq_len(nrow(starwars10)), 1, drop=FALSE], df)) }) @@ -78,3 +78,10 @@ test_that("issue 107", { expect_error(write_ods(legend, tmp, sheet = "Legend", update = TRUE), NA) expect_error(write_ods(legend, tmp, sheet = "Legend2", append = TRUE), NA) }) + +test_that("reading and writing and reading gets the same result as the start", { + a <- read_ods("../testdata/starwars.ods") + b <- write_ods(a, tmp) + c <- read_ods(tmp) + expect_equal(a,c) +}) diff --git a/vignettes/overview.Rmd b/vignettes/overview.Rmd index 9628f95..5a5e6ac 100644 --- a/vignettes/overview.Rmd +++ b/vignettes/overview.Rmd @@ -34,11 +34,6 @@ You can then read it back from `plant.ods` read_ods("plant.ods") ``` -Reading as a tibble -```{r read_ods_tibble} -read_ods("plant.ods", as_tibble = TRUE) -``` - ## Update and Append You can append another sheet into an existing ods file with the sheet name being "mtcars_ods". From 9d1fa796fb76452effc7fd4d03441b49a2c11d4d Mon Sep 17 00:00:00 2001 From: Peter <44036274+pbrohan@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:26:52 +0100 Subject: [PATCH 4/7] Updated GHA for v2 --- .github/workflows/R-CMD-check.yaml | 4 ++-- .github/workflows/test-coverage.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index a001758..6b71534 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -3,10 +3,10 @@ on: push: branches: - - v1* + - v2* pull_request: branches: - - v1* + - v2* name: R-CMD-check diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 49e2e89..bb2532f 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -3,10 +3,10 @@ on: push: branches: - - v1* + - v2* pull_request: branches: - - v1* + - v2* name: test-coverage From a867256b5d5347aedd2b6351bc5f33a24d4d14f5 Mon Sep 17 00:00:00 2001 From: Peter <44036274+pbrohan@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:49:12 +0100 Subject: [PATCH 5/7] Update documentation [no ci] --- R/read_ods.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/read_ods.R b/R/read_ods.R index 223aee2..7c7c58b 100644 --- a/R/read_ods.R +++ b/R/read_ods.R @@ -248,7 +248,7 @@ #' #' Default is `"unique"`. #' -#' @return A data frame (\code{data.frame}) containing a representation of data in the (f)ods file. +#' @return A tibble (\code{tibble}) or data frame (\code{data.frame}) containing a representation of data in the (f)ods file. #' @author Peter Brohan , Chung-hong Chan , Gerrit-Jan Schutten #' @examples #' \dontrun{ From af079c194b411617936e869e3bdd66d25c10343f Mon Sep 17 00:00:00 2001 From: Peter <44036274+pbrohan@users.noreply.github.com> Date: Mon, 24 Jul 2023 23:05:18 +0100 Subject: [PATCH 6/7] Update NEWS.md [no ci] --- NEWS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5c341e1..12aff75 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,7 +13,11 @@ length(list_ods_sheets("starwars.ods")) * `overwrite`: always TRUE * `verbose`: always FALSE * `na_as_string`: default to FALSE - +* +## BREAKING CHANGES: read_ods now outputs as tibble by default +* Added `as_tibble` and `.name_repair` as arguments. If `as_tibble` is true, outputs as a tibble using `tibble::as_tibble()` passing on `.name_repair` (default being `"unique"`). **By default** `as_tibble` is set to TRUE. +* Removed `check_names` argument. All name repairs are now dealt with using `vctrs::vec_as_names()`. This will **significantly change** the default names given to outputs. (Names in the style of `check_names = TRUE` can be obtained by setting `.name_repair = minimal`, although this is not advised) + # readODS 1.9.0 * Added a `NEWS.md` file to track changes to the package. @@ -27,17 +31,13 @@ These have been deprecated for several years. * Added `include_external_data` as an argument (`FALSE` by default). This hides stored data from external sources not normally accessible to the user. -## BREAKING CHANGES: read_ods now outputs as tibble by default -* Added `as_tibble` and `.name_repair` as arguments. If `as_tibble` is true, outputs as a tibble using `tibble::as_tibble()` passing on `.name_repair` (default being `"unique"`). **By default** `as_tibble` is set to TRUE. -* Removed `check_names` argument. All name repairs are now dealt with using `vctrs::vec_as_names()`. This will **significantly change** the default names given to outputs. (Names in the style of `check_names = TRUE` can be obtained by setting `.name_repair = minimal`, although this is not advised) - ## read_ods * Changed behaviour when only one row is read. The row now correctly appears as the top row in the dataframe, and a warning is given if column headers are requested that this would cause the output to be empty (**Note:** in this case column names are not assigned) * Changed behaviour when only one column is read. Previously gave an error. If row names are requested, gives a warning that this would cause the output to be empty, and does not assign names. * Sheets are now accepted as part of the `range` argument, e.g. `Range = "Sheet2!A2:B7"`. If this and the `sheets` argument are given, this is preferred. * Merged cells now have their value places in the top-left cell. All other cells that would be covered by the merge are filled with `NA`. - +* Added `as_tibble` and `.name_repair` as arugments. If `as_tibble` is true, outputs as a tibble using `tibble::as_tibble()` passing on `.name_repair` (default being `"check_unique"`). ## read_fods From 0ba00e91d04ffece2d84fb380f80022c38edef03 Mon Sep 17 00:00:00 2001 From: Peter <44036274+pbrohan@users.noreply.github.com> Date: Tue, 25 Jul 2023 09:29:27 +0100 Subject: [PATCH 7/7] Update read_ods.R --- R/read_ods.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/R/read_ods.R b/R/read_ods.R index 7c7c58b..61dc65f 100644 --- a/R/read_ods.R +++ b/R/read_ods.R @@ -188,7 +188,11 @@ } if(strings[1] == 0 || strings[2] == 0) { warning("empty sheet, return empty data frame.", call. = FALSE) - return(data.frame()) + if(as_tibble){ + return(tibble::tibble()) + } else { + return(data.frame()) + } } res <- as.data.frame( matrix(