Making the CECPfuns package: my own usable package

This is very much work in progress so look for later posts about CECPfuns as well as this.

Chris Evans https://www.psyctc.org/R_blog/ (PSYCTC.org)https://www.psyctc.org/psyctc/
2021-02-10

Latest update

[Started 10.ii.21, tweak 15.iv.21 to add “, build_manual = TRUE” to install_github call]

Background

I have been meaning to do this for years but I have still found it one of R’s more tough learning curves even by R’s sometimes challenging standards. The tidyverse is perhaps a higher and better known climb but making this package is K2 to the tidyverse Everest: nastier, colder, more dispiriting and my attempt of the ascent a few years ago hardly got beyond base camp. This time I’m sort at a first support camp level above base camp and I’m trying to document things here.

Why do it?

I would have saved many hours over the last few years had I actually got on top of this when I first started. Why:

Warning

There are hugely powerful tools to help the creation of R packages and many pages and PDFs on the web to help you. However, for me finding exactly the information I need, getting its context, being sure the advice isn’t outdated and sometimes just understanding what people have written has not always been easy. That’s partly why I’ve created this.

Please, I will try to remember to amend any mistakes I find in here, or things I discover change or can be done more easily than whatever I say here, but anything here doesn’t work for you, please:

  1. look at the “Latest update” date above;
  2. use the search function (in the navigation bar above) and search for “CECPfuns” and look for more recent posts about this;
  3. use an advanced search on the web to search for the particular topic looking for things since that “Latest update” date;
  4. contact me to tell me, ideally tell me how to fix what didn’t work for you;
  5. please realise this is not my job, this, as with everything I put on the web is offered with no warranties, I accept no liabilities, and I probably will have very little time to try to help you explore anything … if I really have time on my hands though, I will try to help. I am doing this using the the Rstudio package building tools, it’s highly unlikely that I will be any help with any other ways of building a package (there are several but I see them as mostly for real R and software experts).

Hm, that I’m writing that probably conveys that this has been a bit tricky.

OK, not K2, actually my view in the Alps, see (www.psyctc.org/pelerinage2016/

Create your package

OK, the first bit is easy: create a new package using a new directory and the “Create R package” option; give your package a name, e.g. “SillyDemoPackage”. There is the option to include some source (i.e. R code for our purposes) files here but I would recommend starting completely cleanly and creating new source files, one per function, and copying and pasting the code you already have into the new file.

That will have created a subdirectory of wherever you were titled named “SillyDemoPackage” and beneath it you have three more subdirectories:

Create your first function

That’s your next step: create a new R script file; if your function is myFunction() then save the script into the R subdirectory that creating the project will have created.

You now have a single source file with a single function in it. (I think you can put more than one function in a single source file but I think it would be making your life more difficult so don’t).

Put your cursor inside the function then go to the Code menu above and select “Insert Roxygen Skeleton”. Let’s say I start with this:

myFunction <- function(x){
  return(paste("This is a silly function of", x))
}

Start to insert help/documentation contents

Using Code, Insert Roxygen Skeleton changes that to this

#' Title
#'
#' @param x 
#'
#' @return
#' @export
#'
#' @examples
myFunction <- function(x){
  return(paste("This is a silly function of", x))
}

And you now change that to this:

#' Title
#'    This is a very silly function purely for demonstration.
#' @param x can be any printable object
#'
#' @return a string that pastes a silly comment before x
#' @export
#'
#' @examples
#' x <- "stunning silliness"
#' myFunction(x)

myFunction <- function(x){
  return(paste("This is a silly function of", x))
}

You see I have given a short description of the function, I have clarified the one parameter (argument) to the function and what the function returns and I have given a suitably inane example of how it might be used.

Now put your cursor in the function and type devtools::document(). That will (essentially invisibly) create a new file myFunction.Rd in the /man subdirectory I mentioned above. **Remember to rerun devtools::document() within the function every time you tweak the documentation in those header lines and every time you tweak the function otherwise the help will lag behind what you’ve done (which might or might not be caught at the next stage, but better safe than sorry.)

Now check and build your package

Now the exciting bit: under the Build menu, pick “Check” and sit back and watch Rstudio and devtools (and perhaps other things for all I know) whiz through many checks on your package (in the top right hand pane of Rstudio in the usual layout, in the Build tab. I don’t think you can miss it. Those checks can flag up erorrs, warnings and notes and you hope to see an all green summary line at the end saying there were none of any of those. If the checks find issues some messages are very clear and helpful, some are more challenging but I have found that searching on the web usually translates them for me.

I would then make sure you set up version control on the project using git and I would also recommend then pushing the package to GitHub if you want others to be able to find it easily.

That’s it! You’re done!

OK, I lie. That’s it for the SillyDemoPackage and it’s one function, myFunction(). I think that’s a like a short afternoon stroll out of Kathmandu in summer. When you start to do something remotely useful the gradient goes up a bit and the air gets a little thinner.

Using functions from other packages

This is a huge issue but actually fairly easy to handle. Most useful functions will call on functions from packages outside of the base functions. Where you do this you need to handle declaring these in a way that means that the package will know what comes from where. There are simple and more sophisticated issues here and the Build, Clean error messages are pretty clear and helpful and there are good guides to the subtleties on the web. So far I have stayed with making the function calls explicit so instead of cor(x, y) I write stats::cor(x, y) in the function and then I add:

Suggests:
  stats

at the bottom of the DESCRIPTION file in the root directory of the package and

importFrom("stats", "cor")

at the bottom of the NAMESPACE file, also in the root directory of the package. I think usethis::use_package() helps with this but I have done it manually so far.

The other thing you have to do at the head of any such function instead of having a

library(sausages) # I wouldn't have had this for stats as, of course,
### the stats package is launched by default when R starts, 
### imagine I am calling sausages::recipe() 
### NO! I made that up!

you use:

invisible(stopifnot(requireNamespace("sausages")))
### so a call that doesn't spit out a message but will stop things 
### if you don't have the sausages package on your system
### requireNamespace() only checks if you have the package
### it doesn't load the entire package as library() or 
### require() would so if you are only going to call one
### or a few functions explicitly with sausages::functionName()
### this is more efficient

That’s the lightest way to do things. If you are going to use lots of functions from a package you may be better with other options but this works for me for now.

How I am synching my package to machines other than my main machine

Added 28.ii.21: dept to Winston Change! If you’re using M$ Windoze I think it’s best to ignore this section. Because Windoze won’t let anything alter a dll on disc that has been loaded into memory, with the really rather complicated way that R (and Rstudio too) pull things into memory as they launch and run .Rprofile this tends to lead to some package upgrading being blocked, e.g. of cachem which Winston maintains.

I am developing my package on my main Linux laptop. As I can’t really survive without it, I have a near duplicate backup machine and a little, old Windows laptop and Windows in a VM on the main machine and I have R on my web server (serving up this blog, my CORE work https://www.coresystemtrust.org.uk/; my non-CORE work site https://www.psyctc.org/psyctc/; and my personal web site: https://www.psyctc.org/pelerinage2016/. Do go and have a look!)

I wanted to make sure that every time I (or cron) launched R on any of the those machines it would automatically check for an update to the package on GitHub and install it if there were one. That meant putting a call to install it with devtools::install_github("cpsyctc/CECPfuns", build_vignettes = TRUE, build_manual = TRUE) into .Rprofile.

Added evening 18.ii.21 with input from Clara ### Locating your .Rprofile file You should find, or create that in locations that are operating system dependent:
* on linux machines it is /home/username/.Rprofile
* on Windows machines it is C:/Users/username/Documents/.Rprofile
* on Macs I am told it is /Users/username/.Rprofile and I am also told that as it is a hidden file, you will need cmd + shift + [.] in order to show the hidden files.

Added evening 10.ii.21, with help from Bill Dunlap via the R-help Email list However, my original addition to .Rprofile cause R to keep looping when launched. Bill Dunlap confirmed that’s because something, probably invoked by the devtools::install_github("cpsyctc/CECPfuns", build_vignettes = TRUE, build_manual = TRUE) call, is restarting the R session and so rerunning the .Rprofile, and so on ad infinitum and Bill gave me the answer so my .Rprofile is now:

if (Sys.getenv("INSTALLING_FROM_GITHUB", unset = "no") == "no") {
  Sys.setenv(INSTALLING_FROM_GITHUB = "yes")
  devtools::install_github("cpsyctc/CECPfuns", build_vignettes = TRUE, build_manual = TRUE)
}

As I understand that code, it checks for an environment variable (i.e. a variable set in the operating system) called “INSTALLING_FROM_GITHUB” and if it finds its value is “no” it runs the the commands inside the brackets, resetting the variable to “yes” and then, the next line, checking if there has been an update of the package on GitHub and installing it if there has been. However, if/when .Rprofile is rerun in that R session the environment variable now has the value “yes” so the looping is prevented. Lovely!

Things that are still work in progress for me!

CECPfuns is a start

This is pretty embarrassing but I will share that this first actual package of mine, probably the only one I’ll ever need to create, is available if you want to see what I’ve managed to create. It will develop into a package mainly of functions I and Clara Paz have found useful (with, I hope, comments and suggestions from Emily) It’s at https://github.com/cpsyctc/CECPfuns and there is a web site for the package at https://cecpfuns.psyctc.org/. You can use git on pretty much any operating system to pull a copy from github if you want to look at the all the raw constituent parts and I think if you do pull that you can see the commit history, i.e. of the changes and updating. (A graph of the commits against date is at https://github.com/cpsyctc/CECPfuns/graphs/commit-activity). I am not opening it to submissions as it’s too early in my learning, I may never reach that place, so, if you have suggestions or corrections and any comments really,
contact me through my work site. I hope this helps someone and encourages them to create their own package. I do wish I’d done it earlier!

Mont Blanc from my Alpine balcony

Reuse

Text and figures are licensed under Creative Commons Attribution CC BY-SA 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".

Citation

For attribution, please cite this work as

Evans (2021, Feb. 10). Chris (Evans) R SAFAQ: Making the CECPfuns package: my own usable package. Retrieved from https://www.psyctc.org/R_blog/posts/2021-02-10-making-my-first-usable-package/

BibTeX citation

@misc{evans2021making,
  author = {Evans, Chris},
  title = {Chris (Evans) R SAFAQ: Making the CECPfuns package: my own usable package},
  url = {https://www.psyctc.org/R_blog/posts/2021-02-10-making-my-first-usable-package/},
  year = {2021}
}