From 649a8025aabfc2b41f99951757b4317d635f341b Mon Sep 17 00:00:00 2001 From: "Matthijs S. Berends" Date: Wed, 27 Jun 2018 15:54:56 +0200 Subject: [PATCH] add clipboard functions again --- DESCRIPTION | 6 +- NAMESPACE | 9 ++ NEWS.md | 1 + R/clipboard.R | 178 ++++++++++++++++++++++++++++++++ R/misc.R | 52 ++++++++++ man/clipboard.Rd | 105 +++++++++++++++++++ man/figures/clipboard_copy.png | Bin 0 -> 2283 bytes man/figures/clipboard_paste.png | Bin 0 -> 4620 bytes man/figures/clipboard_rsi.png | Bin 0 -> 3505 bytes tests/testthat/test-clipboard.R | 9 ++ 10 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 R/clipboard.R create mode 100644 man/clipboard.Rd create mode 100644 man/figures/clipboard_copy.png create mode 100644 man/figures/clipboard_paste.png create mode 100644 man/figures/clipboard_rsi.png create mode 100644 tests/testthat/test-clipboard.R diff --git a/DESCRIPTION b/DESCRIPTION index ca6ad891..32838e50 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AMR -Version: 0.2.0.9005 -Date: 2018-06-20 +Version: 0.2.0.9006 +Date: 2018-06-28 Title: Antimicrobial Resistance Analysis Authors@R: c( person( @@ -28,12 +28,14 @@ Depends: R (>= 3.0.0) Imports: backports, + clipr, curl, dplyr (>= 0.7.0), data.table (>= 1.10.0), reshape2 (>= 1.4.0), xml2 (>= 1.0.0), knitr (>= 1.0.0), + readr, rvest (>= 0.3.2), tibble Suggests: diff --git a/NAMESPACE b/NAMESPACE index d9fe12d7..e739d96c 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -28,6 +28,8 @@ export(as.rsi) export(atc_ddd) export(atc_groups) export(atc_property) +export(clipboard_export) +export(clipboard_import) export(first_isolate) export(freq) export(frequency_tbl) @@ -63,6 +65,8 @@ exportMethods(print.tbl) exportMethods(print.tbl_df) exportMethods(summary.mic) exportMethods(summary.rsi) +importFrom(clipr,read_clip_tbl) +importFrom(clipr,write_clip) importFrom(curl,nslookup) importFrom(data.table,data.table) importFrom(dplyr,"%>%") @@ -70,6 +74,7 @@ importFrom(dplyr,all_vars) importFrom(dplyr,any_vars) importFrom(dplyr,arrange) importFrom(dplyr,arrange_at) +importFrom(dplyr,as_tibble) importFrom(dplyr,between) importFrom(dplyr,desc) importFrom(dplyr,filter) @@ -98,6 +103,8 @@ importFrom(graphics,axis) importFrom(graphics,barplot) importFrom(graphics,plot) importFrom(graphics,text) +importFrom(readr,locale) +importFrom(readr,parse_guess) importFrom(reshape2,dcast) importFrom(rvest,html_children) importFrom(rvest,html_node) @@ -109,4 +116,6 @@ importFrom(stats,sd) importFrom(utils,browseVignettes) importFrom(utils,object.size) importFrom(utils,packageDescription) +importFrom(utils,read.delim) +importFrom(utils,write.table) importFrom(xml2,read_html) diff --git a/NEWS.md b/NEWS.md index c41196fb..9e1c176e 100755 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ * Function `top_freq` function to get the top/below *n* items of frequency tables * Vignette about frequency tables * Possibility to globally set the default for the amount of items to print in frequency tables (`freq` function), with `options(max.print.freq = n)` +* Functions `clipboard_import` and `clipboard_export` as helper functions to quickly copy and paste from/to software like Excel and SPSS #### Changed * Renamed `toConsole` parameter of `freq` function to `as.data.frame` diff --git a/R/clipboard.R b/R/clipboard.R new file mode 100644 index 00000000..dde0d70d --- /dev/null +++ b/R/clipboard.R @@ -0,0 +1,178 @@ +#' Import/export from clipboard +#' +#' @description These are helper functions around \code{\link{read.table}} and \code{\link{write.table}} to import from and export to clipboard with support for Windows, Linux and macOS. +#' +#' The data will be read and written as tab-separated by default, which makes it possible to copy and paste from other software like Excel and SPSS without further transformation. +#' +#' Supports automatic column type transformation and supports new classes \code{\link{as.rsi}} and \code{\link{as.mic}}. +#' @rdname clipboard +#' @name clipboard +#' @inheritParams utils::read.table +#' @inheritParams utils::write.table +#' @inheritParams readr::locale +#' @param startrow \emph{n}th row to start importing from. When \code{header = TRUE}, the import will start on row \code{startrow} \emph{below} the header. +#' @param as_vector a logical value indicating whether data consisting of only one column should be imported as vector using \code{\link[dplyr]{pull}}. This will strip off the header. +#' @param guess_col_types a logical value indicating whether column types should be guessed with the \code{readr} package. +#' @param info print info about copying +#' @keywords clipboard clipboard_import clipboard_export import export +#' @importFrom dplyr %>% pull as_tibble +#' @importFrom clipr read_clip_tbl write_clip +#' @importFrom utils read.delim write.table object.size +#' @importFrom readr parse_guess locale +#' @details +#' When using \code{guess_col_types = TRUE}, all column types will be determined automatically with \code{\link[readr]{parse_guess}} from the \code{readr} package. Besides, the antimicrobial classes in this AMR package (\code{\link{as.rsi}} and \code{\link{as.mic}}) are also supported. +#' +#' \if{html}{ +#' \strong{Example for copying from Excel:} +#' \out{
}\figure{clipboard_copy.png}\out{
} +#' \cr +#' \strong{And pasting in R:} \cr +#' \cr +#' \code{> data <- clipboard_import()} \cr +#' \code{> data} \cr +#' \out{
}\figure{clipboard_paste.png}\out{
} +#' \cr +#' \strong{The resulting data contains the right RSI-classes:} \cr +#' \cr +#' \code{> data$amox} \cr +#' \out{
}\figure{clipboard_rsi.png}\out{
} +#' } +#' @export +clipboard_import <- function(sep = '\t', + header = TRUE, + dec = ".", + na = c("", "NA", "NULL"), + startrow = 1, + as_vector = TRUE, + guess_col_types = TRUE, + date_names = 'en', + date_format = '%Y-%m-%d', + time_format = '%H:%M', + tz = Sys.timezone(), + encoding = "UTF-8", + info = FALSE) { + + if (clipr::clipr_available() & info == TRUE) { + cat('Importing from clipboard...') + } + + # this will fail when clipr is not available + import_tbl <- clipr::read_clip_tbl(file = file, + sep = sep, + header = header, + strip.white = TRUE, + dec = dec, + na.strings = na, + encoding = 'UTF-8', + stringsAsFactors = FALSE) + + if (info == TRUE) { + cat('OK\n') + } + + # use tibble, so column types will be translated correctly + import_tbl <- as_tibble(import_tbl) + + if (startrow > 1) { + # would else lose column headers + import_tbl <- import_tbl[startrow:nrow(import_tbl),] + } + + colnames(import_tbl) <- gsub('[.]+', '_', colnames(import_tbl)) + + if (guess_col_types == TRUE) { + if (info == TRUE) { + cat('Transforming columns with readr::parse_guess...') + } + import_tbl <- clipboard_format(tbl = import_tbl, + date_names = date_names, + date_format = date_format, + time_format = time_format, + decimal_mark = dec, + tz = tz, + encoding = encoding, + na = na) + if (info == TRUE) { + cat('OK\n') + } + } + + if (NCOL(import_tbl) == 1 & as_vector == TRUE) { + import_tbl <- import_tbl %>% pull(1) + } + + if (info == TRUE) { + cat("Successfully imported from clipboard:", NROW(import_tbl), "obs. of", NCOL(import_tbl), "variables.\n") + } + + import_tbl + +} + +#' @rdname clipboard +#' @importFrom dplyr %>% pull as_tibble +#' @export +clipboard_export <- function(x, + sep = '\t', + dec = ".", + na = "", + header = TRUE, + info = TRUE) { + + clipr::write_clip(content = x, + na = na, + sep = sep, + row.names = FALSE, + col.names = header, + dec = dec, + quote = FALSE) + + if (info == TRUE) { + cat("Successfully exported to clipboard:", NROW(x), "obs. of", NCOL(x), "variables.\n") + } + +} + +clipboard_format <- function(tbl, + date_names = 'en', + date_format = '%Y-%m-%d', + time_format = '%H:%M', + decimal_mark = '.', + tz = Sys.timezone(), + encoding = "UTF-8", + na = c("", "NA", "NULL")) { + + date_format <- date_generic(date_format) + time_format <- date_generic(time_format) + # set col types with readr + for (i in 1:ncol(tbl)) { + if (!all(tbl %>% pull(i) %>% class() %in% c('list', 'matrix'))) { + tbl[, i] <- readr::parse_guess(x = tbl %>% pull(i) %>% as.character(), + na = na, + locale = readr::locale(date_names = date_names, + date_format = date_format, + time_format = time_format, + decimal_mark = decimal_mark, + encoding = encoding, + tz = tz, + asciify = FALSE)) + } + if (any(tbl %>% pull(i) %>% class() %in% c('factor', 'character'))) { + # get values + distinct_val <- tbl %>% pull(i) %>% unique() %>% sort() + # remove ASCII escape character: https://en.wikipedia.org/wiki/Escape_character#ASCII_escape_character + tbl[, i] <- tbl %>% pull(i) %>% gsub('\033', ' ', ., fixed = TRUE) + # look for RSI, shouldn't all be "" and must be valid antibiotic interpretations + if (!all(distinct_val[!is.na(distinct_val)] == '') + & all(distinct_val[!is.na(distinct_val)] %in% c('', 'I', 'I;I', 'R', 'R;R', 'S', 'S;S'))) { + tbl[, i] <- tbl %>% pull(i) %>% as.rsi() + } + } + # convert to MIC class + if (colnames(tbl)[i] %like% '_mic$') { + tbl[, i] <- tbl %>% pull(i) %>% as.mic() + } + } + tbl +} + diff --git a/R/misc.R b/R/misc.R index fb6bf27a..3a2a58d9 100755 --- a/R/misc.R +++ b/R/misc.R @@ -114,3 +114,55 @@ size_humanreadable <- function(bytes, decimals = 1) { out <- paste(sprintf(paste0("%.", decimals, "f"), bytes / (1024 ^ factor)), size[factor + 1]) out } + +# transforms date format like "dddd d mmmm yyyy" to "%A %e %B %Y" +date_generic <- function(format) { + if (!grepl('%', format, fixed = TRUE)) { + + # first months and minutes, after that everything is case INsensitive + format <- gsub('mmmm', '%B1', format, fixed = TRUE) + format <- gsub('mmm', '%b', format, fixed = TRUE) + format <- gsub('mm', '%m', format, fixed = TRUE) + format <- gsub('MM', '%M1', format, fixed = TRUE) + format <- format %>% + tolower() %>% + gsub('%b1', '%B', ., fixed = TRUE) %>% + gsub('%m1', '%M', ., fixed = TRUE) + + # dates + format <- gsub('dddd', '%A', format, fixed = TRUE) + format <- gsub('ddd', '%a', format, fixed = TRUE) + format <- gsub('dd', '%!', format, fixed = TRUE) + format <- gsub('d', '%e', format, fixed = TRUE) + format <- gsub('%!', '%d', format, fixed = TRUE) + + format <- gsub('ww', '%V', format, fixed = TRUE) + format <- gsub('w', '%V', format, fixed = TRUE) + + format <- gsub('qq', 'Qq', format, fixed = TRUE) # so will be 'Q%%q' after this + format <- gsub('kk', 'Kq', format, fixed = TRUE) + format <- gsub('k', 'q', format, fixed = TRUE) + format <- gsub('q', '%%q', format, fixed = TRUE) + + format <- gsub('yyyy_iso', '%G', format, fixed = TRUE) + format <- gsub('jjjj_iso', '%G', format, fixed = TRUE) + format <- gsub('yyyy', '%Y', format, fixed = TRUE) + format <- gsub('jjjj', '%Y', format, fixed = TRUE) + format <- gsub('yy_iso', '%g', format, fixed = TRUE) + format <- gsub('jj_iso', '%g', format, fixed = TRUE) + format <- gsub('yy', '%y', format, fixed = TRUE) + format <- gsub('jj', '%y', format, fixed = TRUE) + + # time + format <- gsub('hh', '%H', format, fixed = TRUE) + format <- gsub('h', '%k', format, fixed = TRUE) + format <- gsub('ss', '%S', format, fixed = TRUE) + + # seconds since the Epoch, 1970-01-01 00:00:00 + format <- gsub('unix', '%s', format, fixed = TRUE) + # Equivalent to %Y-%m-%d (the ISO 8601 date format) + format <- gsub('iso', '%F', format, fixed = TRUE) + + } + format +} diff --git a/man/clipboard.Rd b/man/clipboard.Rd new file mode 100644 index 00000000..665fc4d2 --- /dev/null +++ b/man/clipboard.Rd @@ -0,0 +1,105 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/clipboard.R +\name{clipboard} +\alias{clipboard} +\alias{clipboard_import} +\alias{clipboard_export} +\title{Import/export from clipboard} +\usage{ +clipboard_import(sep = "\\t", header = TRUE, dec = ".", na = c("", "NA", + "NULL"), startrow = 1, as_vector = TRUE, guess_col_types = TRUE, + date_names = "en", date_format = "\%Y-\%m-\%d", time_format = "\%H:\%M", + tz = Sys.timezone(), encoding = "UTF-8", info = FALSE) + +clipboard_export(x, sep = "\\t", dec = ".", na = "", header = TRUE, + info = TRUE) +} +\arguments{ +\item{sep}{the field separator character. Values on each line of the + file are separated by this character. If \code{sep = ""} (the + default for \code{read.table}) the separator is \sQuote{white space}, + that is one or more spaces, tabs, newlines or carriage returns.} + +\item{header}{a logical value indicating whether the file contains the + names of the variables as its first line. If missing, the value is + determined from the file format: \code{header} is set to \code{TRUE} + if and only if the first row contains one fewer field than the + number of columns.} + +\item{dec}{the character used in the file for decimal points.} + +\item{na}{the string to use for missing values in the data.} + +\item{startrow}{\emph{n}th row to start importing from. When \code{header = TRUE}, the import will start on row \code{startrow} \emph{below} the header.} + +\item{as_vector}{a logical value indicating whether data consisting of only one column should be imported as vector using \code{\link[dplyr]{pull}}. This will strip off the header.} + +\item{guess_col_types}{a logical value indicating whether column types should be guessed with the \code{readr} package.} + +\item{date_names}{Character representations of day and month names. Either +the language code as string (passed on to \code{\link[=date_names_lang]{date_names_lang()}}) +or an object created by \code{\link[=date_names]{date_names()}}.} + +\item{date_format}{Default date and time formats.} + +\item{time_format}{Default date and time formats.} + +\item{tz}{Default tz. This is used both for input (if the time zone isn't +present in individual strings), and for output (to control the default +display). The default is to use "UTC", a time zone that does not use +daylight savings time (DST) and hence is typically most useful for data. +The absence of time zones makes it approximately 50x faster to generate +UTC times than any other time zone. + +Use \code{""} to use the system default time zone, but beware that this +will not be reproducible across systems. + +For a complete list of possible time zones, see \code{\link{OlsonNames}()}. +Americans, note that "EST" is a Canadian time zone that does not have +DST. It is \emph{not} Eastern Standard Time. It's better to use +"US/Eastern", "US/Central" etc.} + +\item{encoding}{encoding to be assumed for input strings. It is + used to mark character strings as known to be in + Latin-1 or UTF-8 (see \code{\link{Encoding}}): it is not used to + re-encode the input, but allows \R to handle encoded strings in + their native encoding (if one of those two). See \sQuote{Value} + and \sQuote{Note}. + } + +\item{info}{print info about copying} + +\item{x}{the object to be written, preferably a matrix or data frame. + If not, it is attempted to coerce \code{x} to a data frame.} +} +\description{ +These are helper functions around \code{\link{read.table}} and \code{\link{write.table}} to import from and export to clipboard with support for Windows, Linux and macOS. + +The data will be read and written as tab-separated by default, which makes it possible to copy and paste from other software like Excel and SPSS without further transformation. + +Supports automatic column type transformation and supports new classes \code{\link{as.rsi}} and \code{\link{as.mic}}. +} +\details{ +When using \code{guess_col_types = TRUE}, all column types will be determined automatically with \code{\link[readr]{parse_guess}} from the \code{readr} package. Besides, the antimicrobial classes in this AMR package (\code{\link{as.rsi}} and \code{\link{as.mic}}) are also supported. + + \if{html}{ + \strong{Example for copying from Excel:} + \out{
}\figure{clipboard_copy.png}\out{
} + \cr + \strong{And pasting in R:} \cr + \cr + \code{> data <- clipboard_import()} \cr + \code{> data} \cr + \out{
}\figure{clipboard_paste.png}\out{
} + \cr + \strong{The resulting data contains the right RSI-classes:} \cr + \cr + \code{> data$amox} \cr + \out{
}\figure{clipboard_rsi.png}\out{
} + } +} +\keyword{clipboard} +\keyword{clipboard_export} +\keyword{clipboard_import} +\keyword{export} +\keyword{import} diff --git a/man/figures/clipboard_copy.png b/man/figures/clipboard_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..8f2ed9cd2ce9a7e9fdccdb4f26ad0bcf5793884c GIT binary patch literal 2283 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 St73k$Sgu&vTUMESCI1ic3D>;< literal 0 HcmV?d00001 diff --git a/tests/testthat/test-clipboard.R b/tests/testthat/test-clipboard.R new file mode 100644 index 00000000..9429e96f --- /dev/null +++ b/tests/testthat/test-clipboard.R @@ -0,0 +1,9 @@ +context("clipboard.R") + +test_that("clipboard works", { + skip_if_not(clipr::clipr_available()) + + clipboard_export(antibiotics) + expect_identical(antibiotics, + clipboard_import()) +})