Cost distribution among software process activities
Good Software Engineering Practice for R Packages
July 23, 2026
From an idea to a production-grade R package
Example scenario: in your daily work, you notice that you need certain one-off scripts again and again.
The idea of creating an R package was born because you understood that “copy and paste” R scripts is inefficient and on top of that, you want to share your helpful R functions with colleagues and the world…
Photo CC0 by ELEVATE on pexels.com
Photo CC0 by Chevanon Photography on pexels.com
Bad practice!
Why?
Cost distribution among software process activities
Origin of errors in system development
Boehm, B. (1981). Software Engineering Economics. Prentice Hall.
Invest time in
… but in many cases the workflow must be workable for a single developer or a small team.
Photo CC0 by Kateryna Babaieva on pexels.com
Let’s assume that you used some lines of code to create simulated data in multiple projects:
Idea: put the code into a package
| Obligation level | Key word1 | Description |
|---|---|---|
| Duty | shall | “must have” |
| Desire | should | “nice to have” |
| Intention | will | “optional” |
Purpose and Scope
The R package simulatr shall enable the creation of reproducible fake data.
Package Requirements
simulatr shall provide a function to generate normal distributed random data for two independent groups. The function shall allow flexible definition of sample size per group, mean per group, standard deviation per group. The reproducibility of the simulated data shall be ensured via an optional seed It should be possible to print the function result. A graphical presentation of the simulated data will also be possible.
Useful formats / tools for design docs:
UML Diagram

R package programming
One-off script as starting point:
Refactored script:
Almost all functions, arguments, and objects should be self-explanatory due to their names.
Define that the result is a list1 which is defined as class2:
getSimulatedTwoArmMeans <- function(n1, n2, mean1, mean2, sd1, sd2) {
result <- list(n1 = n1, n2 = n2,
mean1 = mean1, mean2 = mean2, sd1 = sd1, sd2 = sd2)
result$data <- data.frame(
group = c(rep(1, n1), rep(2, n2)),
values = c(
rnorm(n = n1, mean = mean1, sd = sd1),
rnorm(n = n2, mean = mean2, sd = sd2)
)
)
# set the class attribute
result <- structure(result, class = "SimulationResult")
return(result)
}The output is impractical, e.g., we need to scroll down:
$n1
[1] 50
$n2
[1] 50
$mean1
[1] 5
$mean2
[1] 7
$sd1
[1] 3
$sd2
[1] 4
$data
group values
1 1 4.66940994
2 1 0.31933787
3 1 2.96272303
4 1 2.70531420
5 1 5.12062835
6 1 2.35548281
7 1 2.57505997
8 1 -0.88399994
9 1 3.63627101
10 1 3.94361037
11 1 4.81529069
12 1 -0.39436274
13 1 0.99314238
14 1 3.86245486
15 1 3.05956322
16 1 5.26162202
17 1 1.40752260
18 1 4.92269100
19 1 7.20619497
20 1 3.76065566
21 1 5.67176326
22 1 5.48324162
23 1 4.57165615
24 1 4.02082846
25 1 1.76495973
26 1 2.15497735
27 1 4.57652508
28 1 5.62678657
29 1 5.39330334
30 1 4.95059693
31 1 11.57762809
32 1 7.51376113
33 1 5.05448260
34 1 6.67430470
35 1 0.83801881
36 1 9.82340707
37 1 4.61023079
38 1 1.96505103
39 1 3.36016238
40 1 0.45187905
41 1 -2.48387431
42 1 2.45670705
43 1 3.00338362
44 1 6.30101612
45 1 -1.13825791
46 1 5.05039573
47 1 -0.17725051
48 1 4.46943294
49 1 10.41500771
50 1 5.14376410
51 2 3.32257554
52 2 -0.83476399
53 2 8.47058612
54 2 8.04365637
55 2 8.88584475
56 2 12.82533998
57 2 13.03905444
58 2 4.66104027
59 2 8.94761758
60 2 6.13961007
61 2 8.51968121
62 2 -0.22076005
63 2 5.61787474
64 2 8.34187355
65 2 7.26682253
66 2 8.76645937
67 2 9.15701444
68 2 4.78205822
69 2 5.38654596
70 2 12.15277922
71 2 8.94837757
72 2 7.54055899
73 2 -2.61793227
74 2 6.82242056
75 2 10.89935295
76 2 8.14174634
77 2 1.84714882
78 2 5.91265325
79 2 8.23455972
80 2 5.95902098
81 2 9.76543228
82 2 6.51640180
83 2 4.19512253
84 2 12.53705228
85 2 7.76524641
86 2 12.78336878
87 2 0.02924049
88 2 5.98756064
89 2 7.50028285
90 2 0.51681177
91 2 7.14619000
92 2 12.85481606
93 2 13.76424623
94 2 7.40377994
95 2 7.41457822
96 2 -0.71216408
97 2 11.74731391
98 2 0.09185472
99 2 14.22757810
100 2 2.69311096
attr(,"class")
[1] "SimulationResult"
Solution: implement generic function print
Generic function print:
#' @title
#' Print Simulation Result
#'
#' @description
#' Generic function to print a `SimulationResult` object.
#'
#' @param x a \code{SimulationResult} object to print.
#' @param ... further arguments passed to or from other methods.
#'
#' @examples
#' x <- getSimulatedTwoArmMeans(n1 = 50, n2 = 50, mean1 = 5,
#' mean2 = 7, sd1 = 3, sd2 = 4, seed = 123)
#' print(x)
#'
#' @export$args
n1 n2 mean1 mean2 sd1 sd2
"50" "50" "5" "7" "3" "4"
$data
# A tibble: 100 × 2
group values
<dbl> <dbl>
1 1 4.67
2 1 0.319
3 1 2.96
4 1 2.71
5 1 5.12
6 1 2.36
7 1 2.58
8 1 -0.884
9 1 3.64
10 1 3.94
# ℹ 90 more rows
pkgdownpkgdownpkgdown makes it quick and easy to build a website for your packagepkgdown, just use usethis::use_pkgdown() to get started_pkgdown.yml filereference section updated with names of .Rd files_pkgdown.yml file---
url: https://openpharma.github.io/mmrm
template:
bootstrap: 5
params:
ganalytics: UA-125641273-1
navbar:
right:
- icon: fa-github
href: https://github.com/openpharma/mmrm
reference:
- title: Package
contents:
- mmrm-package
- title: Functions
contents:
- mmrm
- fit_mmrm
- mmrm_control
- fit_single_optimizer
- refit_multiple_optimizers
- df_1d
- df_md
- componentgh-pages that stores the rendered websitemain branch is updatedusethis::use_pkgdown_github_pages()
pkgdown::deploy_to_branch()Photo CC0 by Pixabay on pexels.com
Add assertions to improve the usability and user experience
Tip on assertions
Use the package checkmate to validate input arguments.
Example:
Error in playWithAssertions(-1) : Assertion on ‘n1’ failed: Element 1 is not >= 1.
Add three additional results:
Tip on creation time
Sys.time(), format(Sys.time(), '%B %d, %Y'), Sys.Date()
Add an additional result: t.test result
Add an optional alternative argument and pass it through t.test:
Implement the generic functions print and plot.
Tip on print
Use the plot example function from above and extend it.
Optional extra tasks:
Implement the generic functions summary and cat
Implement the function kable known from the package knitr as generic. Tip: use
to define kable as generic
Optional extra task1:
Document your functions with Roxygen2