{"id":13942438,"url":"https://github.com/nanxstats/r-base-shortcuts","last_synced_at":"2025-04-05T01:05:29.670Z","repository":{"id":176528449,"uuid":"658506173","full_name":"nanxstats/r-base-shortcuts","owner":"nanxstats","description":"⚡ Base R shortcuts: A collection of lesser-known but powerful idioms and coding patterns for writing concise and fast R code","archived":false,"fork":false,"pushed_at":"2025-03-02T06:15:10.000Z","size":1590,"stargazers_count":159,"open_issues_count":0,"forks_count":17,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-29T00:06:15.089Z","etag":null,"topics":["design-patterns","ergonomics","idiomatic","idiomatic-r","idioms","r-base","r-language","rstats"],"latest_commit_sha":null,"homepage":"https://nanx.me/blog/post/r-base-shortcuts/","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nanxstats.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":".github/CODE-OF-CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-25T23:54:04.000Z","updated_at":"2025-03-22T10:17:05.000Z","dependencies_parsed_at":"2024-11-16T23:36:44.821Z","dependency_job_id":null,"html_url":"https://github.com/nanxstats/r-base-shortcuts","commit_stats":null,"previous_names":["nanxstats/r-base-shortcuts"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanxstats%2Fr-base-shortcuts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanxstats%2Fr-base-shortcuts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanxstats%2Fr-base-shortcuts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanxstats%2Fr-base-shortcuts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nanxstats","download_url":"https://codeload.github.com/nanxstats/r-base-shortcuts/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247271522,"owners_count":20911587,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["design-patterns","ergonomics","idiomatic","idiomatic-r","idioms","r-base","r-language","rstats"],"created_at":"2024-08-08T02:01:52.095Z","updated_at":"2025-04-05T01:05:29.648Z","avatar_url":"https://github.com/nanxstats.png","language":"Shell","funding_links":[],"categories":["Shell"],"sub_categories":[],"readme":"# r-base-shortcuts\n\n\u003ca href=\"https://nanx.me/blog/post/r-base-shortcuts/\"\u003e\n\u003cimg src=\"images/banner.png\" alt=\"r-base-shortcuts\"\u003e \u003c/a\u003e\n\nA collection of lesser-known but powerful base R idioms and shortcuts\nfor writing concise and fast base R code, useful for beginner level to\nintermediate level R developers.\n\nPlease help me improve and extend this list.\nSee [contributing guide](.github/CONTRIBUTING.md)\nand [code of conduct](.github/CODE-OF-CONDUCT.md).\n\n\u003e Why?\n\u003e\n\u003e From 2012 to 2022, I answered thousands of R questions in the\n\u003e online community [Capital of Statistics](https://d.cosx.org/).\n\u003e These recipes are observed and digested from the recurring patterns\n\u003e I learned from the frequently asked questions with less common answers.\n\n## Contents\n\n- [Object creation](#object-creation)\n  - [Create sequences with `seq_len()` and `seq_along()`](#create-sequences-with-seq_len-and-seq_along)\n  - [Repeat character strings with `strrep()`](#repeat-character-strings-with-strrep)\n  - [Create an empty list of a given length](#create-an-empty-list-of-a-given-length)\n  - [Create and assigning S3 classes in one step](#create-and-assigning-s3-classes-in-one-step)\n  - [Assign names to vector elements or data frame columns at creation](#assign-names-to-vector-elements-or-data-frame-columns-at-creation)\n  - [Use `I()` to include objects as is in data frames](#use-i-to-include-objects-as-is-in-data-frames)\n  - [Generate factors using `gl()`](#generate-factors-using-gl)\n- [Object transformation](#object-transformation)\n  - [Insert elements into a vector with `append()`](#insert-elements-into-a-vector-with-append)\n  - [Modify data frames with `transform()`](#modify-data-frames-with-transform)\n  - [Modify data frames with `within()`](#modify-data-frames-with-within)\n  - [Use `[` and `[[` as functions in apply calls](#use--and--as-functions-in-apply-calls)\n  - [Sum all components in a list](#sum-all-components-in-a-list)\n  - [Bind multiple data frames in a list](#bind-multiple-data-frames-in-a-list)\n  - [Use `modifyList()` to update a list](#use-modifylist-to-update-a-list)\n  - [Use `aperm()` and `asplit()` to permute and split arrays](#use-aperm-and-asplit-to-permute-and-split-arrays)\n  - [Run-length encoding](#run-length-encoding)\n- [Conditions](#conditions)\n  - [Use `inherits()` for class checking](#use-inherits-for-class-checking)\n  - [Replace multiple `ifelse()` with `cut()`](#replace-multiple-ifelse-with-cut)\n  - [Simplify recoding categorical values with `factor()`](#simplify-recoding-categorical-values-with-factor)\n  - [Save the number of `if` conditions with upcasting](#save-the-number-of-if-conditions-with-upcasting)\n  - [Use `findInterval()` for many breakpoints](#use-findinterval-for-many-breakpoints)\n- [Vectorization](#vectorization)\n  - [Use `match()` for fast lookups](#use-match-for-fast-lookups)\n  - [Use environments as fast key-value stores for fast lookups](#use-environments-as-fast-key-value-stores-for-fast-lookups)\n  - [Use `mapply()` for element-wise operations on multiple lists](#use-mapply-for-element-wise-operations-on-multiple-lists)\n  - [Simplify element-wise min and max operations with `pmin()` and `pmax()`](#simplify-element-wise-min-and-max-operations-with-pmin-and-pmax)\n  - [Apply a function to all combinations of parameters](#apply-a-function-to-all-combinations-of-parameters)\n  - [Generate all possible combinations of given characters](#generate-all-possible-combinations-of-given-characters)\n  - [Vectorize a function with `Vectorize()`](#vectorize-a-function-with-vectorize)\n  - [Pairwise computations using `outer()`](#pairwise-computations-using-outer)\n  - [Subtract column means from non-zero elements in a sparse matrix](#subtract-column-means-from-non-zero-elements-in-a-sparse-matrix)\n- [Functions](#functions)\n  - [Specify formal argument lists with `alist()`](#specify-formal-argument-lists-with-alist)\n  - [Use internal functions without `:::`](#use-internal-functions-without-)\n- [Side-effects](#side-effects)\n  - [Return invisibly with `invisible()` for side-effect functions](#return-invisibly-with-invisible-for-side-effect-functions)\n  - [Use `on.exit()` for cleanup](#use-onexit-for-cleanup)\n- [Numerical computations](#numerical-computations)\n  - [Create step functions with `stepfun()`](#create-step-functions-with-stepfun)\n- [Further reading](#further-reading)\n\n## Object creation\n\n### Create sequences with `seq_len()` and `seq_along()`\n\n`seq_len()` and `seq_along()` are safer than `1:length(x)` or `1:nrow(x)`\nbecause they avoid the unexpected result when `x` is of length `0`:\n\n```r\n# Safe version of 1:length(x)\nseq_len(length(x))\n# Safe version of 1:length(x)\nseq_along(x)\n```\n\n### Repeat character strings with `strrep()`\n\nWhen you need to repeat a string a certain number of times, instead of using\nthe tedious pattern of `paste(rep(\"foo\", 10), collapse = \"\")`, you can use\nthe `strrep()` function:\n\n```r\nstrrep(\"foo\", 10)\n```\n\n`strrep()` is vectorized, meaning that you can pass vectors as arguments and\nit will return a vector of the same length as the first argument:\n\n```r\nfruits \u003c- c(\"apple\", \"banana\", \"orange\")\nstrrep(c(\"*\"), nchar(fruits))\nstrrep(c(\"-\", \"=\", \"**\"), nchar(fruits))\n```\n\n### Create an empty list of a given length\n\nUse the `vector()` function to create an empty list of a specific length:\n\n```r\nx \u003c- vector(\"list\", length)\n```\n\n### Create and assigning S3 classes in one step\n\nAvoid creating an object and assigning its class separately.\nInstead, use the `structure()` function to do both at once:\n\n```r\nx \u003c- structure(list(), class = \"my_class\")\n```\n\nInstead of:\n\n```r\nx \u003c- list()\nclass(x) \u003c- \"my_class\"\n```\n\nThis makes the code more concise when returning an object of a specific class.\n\n### Assign names to vector elements or data frame columns at creation\n\nThe `setNames()` function allows you to assign names to vector elements or\ndata frame columns during creation:\n\n```r\nx \u003c- setNames(1:3, c(\"one\", \"two\", \"three\"))\nx \u003c- setNames(data.frame(...), c(\"names\", \"of\", \"columns\"))\n```\n\n### Use `I()` to include objects as is in data frames\n\nThe `I()` function allows you to include objects as is when creating data frames:\n\n```r\ndf \u003c- data.frame(x = I(list(1:10, letters)))\ndf$x\n#\u003e [[1]]\n#\u003e  [1]  1  2  3  4  5  6  7  8  9 10\n#\u003e\n#\u003e [[2]]\n#\u003e  [1] \"a\" \"b\" \"c\" \"d\" \"e\" \"f\" \"g\" \"h\" \"i\" \"j\" \"k\" \"l\" \"m\"\n#\u003e [14] \"n\" \"o\" \"p\" \"q\" \"r\" \"s\" \"t\" \"u\" \"v\" \"w\" \"x\" \"y\" \"z\"\n```\n\nThis creates a data frame with one column `x` that is a list of vectors.\n\n### Generate factors using `gl()`\n\nCreate a vector with specific levels with `gl()` by specifying the levels\nand the number of repetitions:\n\n```r\ngl(n = 2, k = 5, labels = c(\"Low\", \"High\"))\n#\u003e [1] Low  Low  Low  Low  Low  High High High High High\n#\u003e Levels: Low High\n```\n\nThe `gl()` function is particularly useful when setting up experiments\nor simulations that involve categorical variables.\n\n## Object transformation\n\n### Insert elements into a vector with `append()`\n\nWhen you need to insert elements into a vector at a specific position,\nuse `append()`. It has an argument `after` that specifies the position after\nwhich the new elements should be inserted, defaulting to length of the vector\nbeing appended to.\n\nFor example, To insert the numbers 4, 5, 6 between 1, 2, 3 and 7, 8, 9:\n\n```r\nx \u003c- c(1, 2, 3, 7, 8, 9)\nappend(x, 4:6, after = 3)\n#\u003e [1] 1 2 3 4 5 6 7 8 9\n```\n\nWithout `append()`, the solution would be more verbose and less readable:\n\n```r\nc(x[1:3], 4:6, x[4:length(x)])\n#\u003e [1] 1 2 3 4 5 6 7 8 9\n```\n\nWhen `after` is set to `0`, the new values are \"appended\" to the beginning of\nthe input vector:\n\n```r\nappend(x, 4:6, after = 0)\n#\u003e [1] 4 5 6 1 2 3 7 8 9\n```\n\n### Modify data frames with `transform()`\n\nWhen adding new columns or modifying existing columns in a data frame,\ninstead of assigning each column individually, use `transform()` to perform\nmultiple transformations in a single step:\n\n```r\ndf \u003c- data.frame(x = 1:5, y = 6:10)\ntransform(df, z = x + y, y = y * 2, w = sqrt(x))\n```\n\nThis is more concise and readable compared to the alternative of\nmultiple assignments and repeating `df$`:\n\n```r\ndf$z \u003c- df$x + df$y\ndf$y \u003c- df$y * 2\ndf$w \u003c- sqrt(df$x)\n```\n\n### Modify data frames with `within()`\n\nFor more complex data transformations that involve multiple steps or\nintermediate variables, consider using the `within()` function\n(not to be confused with `with()`):\n\n```r\ndf \u003c- data.frame(x = 1:5, y = 6:10)\n\nwithin(df, {\n  y \u003c- x / sum(x)\n  z \u003c- log(y)\n  category \u003c- ifelse(z \u003e -2, \"High\", \"Low\")\n})\n```\n\nNote that both `transform()` and `within()` return a modified copy of the\noriginal data frame and does not change the original data frame,\nunless you assign the result back.\n\n### Use `[` and `[[` as functions in apply calls\n\nWhen you need to extract the same element from each item in a list or\nlist-like object, you can leverage `[` and `[[` as functions\n(they actually are!) within `lapply()` and `sapply()` calls.\n\nConsider a list of named vectors:\n\n```r\nlst \u003c- list(\n  item1 = c(a = 1, b = 2, c = 3),\n  item2 = c(a = 4, b = 5, c = 6),\n  item3 = c(a = 7, b = 8, c = 9)\n)\n\n# Extract named element \"a\" using `[[`\nelement_a \u003c- sapply(lst, `[[`, \"a\")\n\nlst \u003c- list(\n  item1 = c(1, 2, 3),\n  item2 = c(4, 5, 6),\n  item3 = c(7, 8, 9)\n)\n\n# Extract first element using `[`\nfirst_element \u003c- sapply(lst, `[`, 1)\n```\n\n### Sum all components in a list\n\nUse the `Reduce()` function with the infix function `+` to sum up all components\nin a list:\n\n```r\nx \u003c- Reduce(\"+\", list)\n```\n\n### Bind multiple data frames in a list\n\nThe `do.call()` function with the `rbind` argument allows you to bind\nmultiple data frames in a list into one data frame:\n\n```r\ndf_combined \u003c- do.call(\"rbind\", list_of_dfs)\n```\n\nAlternatively, more performant solutions for such operations are offered in\n`data.table::rbindlist()` and `dplyr::bind_rows()`. See\n[this article](https://rpubs.com/jimhester/rbind) for details.\n\n### Use `modifyList()` to update a list\n\nThe `modifyList()` function allows you to easily update values in a list\nwithout a verbose syntax:\n\n```r\nold_list \u003c- list(a = 1, b = 2, c = 3)\nnew_vals \u003c- list(a = 10, c = 30)\nnew_list \u003c- modifyList(defaults, new_vals)\n```\n\nThis can be very useful for maintaining and updating a set of\nconfiguration parameters.\n\n### Use `aperm()` and `asplit()` to permute and split arrays\n\nUse `aperm()` and `asplit()` to avoid nested for-loops in array manipulation.\n`aperm()` is the generalization of matrix transpose. `asplit()` can split along\nany array dimension.\n\n```r\narr \u003c- array(\n  1:24,\n  dim = c(2, 3, 4),\n  dimnames = list(\n    row = paste0(\"R\", 1:2),\n    col = paste0(\"C\", 1:3),\n    slice = paste0(\"S\", 1:4)\n  )\n)\n\n# Rearrange dimensions from (2 x 3 x 4) to (4 x 3 x 2)\naperm(arr, perm = c(3, 2, 1))\n\n# Split into a length-4 list of (2 x 3) matrices\nasplit(arr, MARGIN = 3)\n```\n\n### Run-length encoding\n\nRun-length encoding is a simple form of data compression in which sequences\nof the same element are replaced by a single instance of the element followed\nby the number of times it appears in the sequence.\n\nSuppose you have a vector with many repeating elements:\n\n```r\nx \u003c- c(1, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 2, 1, 1)\n```\n\nYou can use `rle()` to compress this vector and decompress the result back\ninto the original vector with `inverse.rle()`:\n\n```r\nx \u003c- c(1, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 2, 1, 1)\n\n(y \u003c- rle(x))\n#\u003e Run Length Encoding\n#\u003e   lengths: int [1:5] 3 2 4 3 2\n#\u003e   values : num [1:5] 1 2 3 2 1\n\ninverse.rle(y)\n#\u003e [1] 1 1 1 2 2 3 3 3 3 2 2 2 1 1\n```\n\n## Conditions\n\n### Use `inherits()` for class checking\n\nInstead of using the `class()` function in conjunction with `==`, `!=`,\nor `%in%` operators to check if an object belongs to a certain class,\nuse the `inherits()` function.\n\n```r\nif (inherits(x, \"class\"))\n```\n\nThis will return `TRUE` if \"class\" is one of the classes from which `x` inherits.\nThis replaces the following more verbose forms:\n\n```r\nif (class(x) == \"class\")\n```\n\nor\n\n```r\nif (class(x) %in% c(\"class1\", \"class2\"))\n```\n\nIt is also more reliable because it checks for class inheritance,\nnot just the first class name (R supports multiple classes for S3 and S4 objects).\n\n### Replace multiple `ifelse()` with `cut()`\n\nFor a series of range-based conditions, use `cut()` instead of chaining\nmultiple `if-else` conditions or `ifelse()` calls:\n\n```r\ncategories \u003c- cut(\n  x,\n  breaks = c(-Inf, 0, 10, Inf),\n  labels = c(\"negative\", \"small\", \"large\")\n)\n```\n\nThis assigns each element in `x` to the category that corresponds to the\nrange it falls in.\n\n### Simplify recoding categorical values with `factor()`\n\nWhen dealing with categorical variables, you might need to replace or\nrecode certain levels. This can be achieved using chained `ifelse()` statements,\nbut a more efficient and readable approach is to use the `factor()` function:\n\n```r\nx \u003c- c(\"M\", \"F\", \"F\", NA)\n\nfactor(\n  x,\n  levels = c(\"F\", \"M\", NA),\n  labels = c(\"Female\", \"Male\", \"Missing\"),\n  exclude = NULL # Include missing values in the levels\n)\n```\n\n### Save the number of `if` conditions with upcasting\n\nSometimes, the number of conditions checked in multiple `if` statements\ncan be reduced by cleverly using the fact that in R,\n`TRUE` is upcasted to `1` and `FALSE` to `0` in numeric contexts.\nThis can be useful for selecting an index based on a set of conditions:\n\n```r\ni \u003c- (width \u003e= 960) + (width \u003e= 1140) + 1\np \u003c- p + facet_wrap(vars(class), ncol = c(1, 2, 4)[i])\n```\n\nThis does the same thing as the following code, but in a much more concise way:\n\n```r\nif (width \u003e= 1140) p \u003c- p + facet_wrap(vars(class), ncol = 4)\nif (width \u003e= 960 \u0026 width \u003c 1140) p \u003c- p + facet_wrap(vars(class), ncol = 2)\nif (width \u003c 960) p \u003c- p + facet_wrap(vars(class), ncol = 1)\n```\n\nThis works because the condition checks in the parentheses result in a\n`TRUE` or `FALSE`, and when they are added together, they are\nupcasted to `1` or `0`.\n\n### Use `findInterval()` for many breakpoints\n\nIf you want to assign a variable to many different groups or intervals,\ninstead of using a series of `if` statements, you can use the\n`findInterval()` function. Using the same example above:\n\n```r\nbreakpoints \u003c- c(960, 1140)\nncols \u003c- c(1, 2, 4)\ni \u003c- findInterval(width, breakpoints) + 1\np \u003c- p + facet_wrap(vars(class), ncol = ncols[i])\n```\n\nThe `findInterval()` function finds which interval each number in a\ngiven vector falls into and returns a vector of interval indices.\nIt's a faster alternative when there are many breakpoints.\n\n## Vectorization\n\n### Use `match()` for fast lookups\n\nThe `match()` function can be faster than `which()` for looking up\nvalues in a vector:\n\n```r\nindex \u003c- match(value, my_vector)\n```\n\nThis code sets `index` to the index of `value` in `my_vector`.\n\n### Use environments as fast key-value stores for fast lookups\n\nHashed environments created by `new.env(hash = TRUE)` can be used as fast\nkey–value store (hash tables).\n\nLookups (to check if a key exists) are effectively O(1) in a hashed environment\nversus O(N) when using a regular list with `names()`.\nThis makes it a much faster and more memory-friendly choice than lists or\nnamed vectors for determining if \"something already exists\".\n\n```r\n# Generate keys\nset.seed(42)\n\nn_keys \u003c- 100000\nkeys \u003c- replicate(n_keys, paste0(sample(letters, 10, replace = TRUE), collapse = \"\"))\n\n# Store in a hashed environment\nhash_env \u003c- new.env(hash = TRUE, size = n_keys)\nfor (k in keys) hash_env[[k]] \u003c- TRUE\n\n# Store in a named list\nmy_list \u003c- vector(\"list\", length(keys))\nnames(my_list) \u003c- keys\n\n# Benchmark\nn_tests \u003c- 50000\ntest_keys \u003c- sample(keys, n_tests, replace = TRUE)\n\nsystem.time(for (k in test_keys) invisible(exists(k, envir = hash_env, inherits = FALSE)))\n#  user  system elapsed\n# 0.044   0.000   0.045\nsystem.time(for (k in test_keys) invisible(k %in% names(my_list)))\n#   user  system elapsed\n# 29.518   2.026  32.129\n```\n\n### Use `mapply()` for element-wise operations on multiple lists\n\n`mapply()` applies a function over a set of lists in an element-wise fashion:\n\n```r\nmapply(sum, list1, list2, list3)\n```\n\n### Simplify element-wise min and max operations with `pmin()` and `pmax()`\n\nWhen comparing two or more vectors on an element-wise basis and get the\nminimum or maximum of each set of elements, use `pmin()` and `pmax()`.\n\n```r\nvec1 \u003c- c(1, 5, 3, 9, 5)\nvec2 \u003c- c(4, 2, 8, 1, 7)\n\n# Instead of using sapply() or a loop:\nsapply(1:length(vec1), function(i) min(vec1[i], vec2[i]))\nsapply(1:length(vec1), function(i) max(vec1[i], vec2[i]))\n\n# Use pmin() and pmax() for a more concise and efficient solution:\npmin(vec1, vec2)\npmax(vec1, vec2)\n```\n\n`pmin()` and `pmax()` perform these operations much more efficiently than\nalternatives such as applying `min()` and `max()` in a loop or using `sapply()`.\nThis can lead to a noticeable performance improvement when working with large vectors.\n\n### Apply a function to all combinations of parameters\n\nSometimes we need to run a function on every combination of a set of\nparameter values, for example, in grid search. We can use the combination of\n`expand.grid()`, `mapply()`, and `do.call()` + `rbind()` to accomplish this.\n\nSuppose we have a simple function that takes two parameters, `a` and `b`:\n\n```r\nf \u003c- function(a, b) {\n  result \u003c- a * b\n  data.frame(a = a, b = b, result = result)\n}\n```\n\nCreate a grid of `a` and `b` parameter values to evaluate:\n\n```r\nparams \u003c- expand.grid(a = 1:3, b = 4:6)\n```\n\nWe use `mapply()` to apply `f` to each row of our parameter grid.\nWe will use `SIMPLIFY = FALSE` to keep the results as a list of data frames:\n\n```r\nlst \u003c- mapply(f, a = params$a, b = params$b, SIMPLIFY = FALSE)\n```\n\nFinally, we bind all the result data frames together into one final data frame:\n\n```r\ndo.call(rbind, lst)\n```\n\n### Generate all possible combinations of given characters\n\nTo generate all possible combinations of a given set of characters,\n`expand.grid()` and `do.call()` with `paste0()` can help.\nThe following snippet produces all possible three-digit character\nstrings consisting of both letters (lowercase) and numbers:\n\n```r\nx \u003c- c(letters, 0:9)\ndo.call(paste0, expand.grid(x, x, x))\n```\n\nHere, `expand.grid()` generates a data frame where each row is a unique\ncombination of three elements from `x`. Then, `do.call(paste0, ...)`\nconcatenates each combination together into a string.\n\n### Vectorize a function with `Vectorize()`\n\nIf a function is not natively vectorized (it has arguments that only take\none value at a time), you can use `Vectorize()` to create a new function\nthat accepts vector inputs:\n\n```r\nf \u003c- function(x) x^2\nlower \u003c- c(1, 2, 3)\nupper \u003c- c(4, 5, 6)\n\nintegrate_vec \u003c- Vectorize(integrate, vectorize.args = c(\"lower\", \"upper\"))\n\nresult \u003c- integrate_vec(f, lower, upper)\nunlist(result[\"value\", ])\n```\n\nThe `Vectorize()` function works internally by leveraging the `mapply()`\nfunction, which applies a function over two or more vectors or lists.\n\n### Pairwise computations using `outer()`\n\nThe `outer()` function is useful for applying a function to every pair of\nelements from two vectors. This can be particularly useful for U-statistics\nand other situations requiring pairwise computations.\n\nConsider two vectors of numeric values for which we wish to compute a\ncustom function for each pair:\n\n```r\nx \u003c- rnorm(5)\ny \u003c- rnorm(5)\n\nouter(x, y, FUN = function(x, y) x + x^2 - y)\n```\n\n### Subtract column means from non-zero elements in a sparse matrix\n\nHere are three methods to achieve this, with increasing levels of optimization.\n\n```r\nlibrary(Matrix)\n\nset.seed(42)\nmat \u003c- rsparsematrix(nrow = 1000, ncol = 500, density = 0.01)\n```\n\n**Method 1**. Loop over columns and subtract the mean for non-zero elements:\n\n```r\nf1 \u003c- function(mat) {\n  col_means \u003c- colSums(mat) / colSums(mat != 0)\n  for (i in seq_len(ncol(mat))) {\n    mat[mat[, i] != 0, i] \u003c- mat[mat[, i] != 0, i] - col_means[i]\n  }\n  mat\n}\n```\n\n**Method 2**. Use a helper matrix to subtract column means with matrix multiplication:\n\n```r\nf2 \u003c- function(mat) {\n  mat_copy \u003c- mat\n  mat_copy@x \u003c- rep(1, length(mat_copy@x))\n  col_means \u003c- colSums(mat) / colSums(mat_copy)\n  mat - mat_copy %*% Diagonal(x = col_means)\n}\n```\n\n**Method 3**. Modify sparse matrix non-zero values directly:\n\n```r\nf3 \u003c- function(mat) {\n  col_means \u003c- colSums(mat) / colSums(mat != 0)\n  mat@x \u003c- mat@x - rep(col_means, diff(mat@p))\n  mat\n}\n```\n\n```r\nmicrobenchmark::microbenchmark(f1(mat), f2(mat), f3(mat), times = 100)\n#\u003e Unit: microseconds\n#\u003e     expr        min          lq        mean      median         uq        max\n#\u003e  f1(mat) 110731.242 113995.1290 133040.4843 115918.4595 119605.159 263641.562\n#\u003e  f2(mat)    473.509    504.6280    680.0215    571.9705    602.659   4543.620\n#\u003e  f3(mat)    172.446    192.5155    278.6069    238.0460    259.448   3965.356\n```\n\nThe speedup is achieved by avoiding making redundant copies (from R's\ncopy-on-modify semantics) and making in-place modifications as much as possible.\n\n## Functions\n\n### Specify formal argument lists with `alist()`\n\nThe `alist()` function can create lists where some elements are intentionally\nleft blank (or are \"missing\"), which can be helpful when we want to specify\nformal arguments of a function, especially in conjunction with `formals()`.\n\nConsider this scenario. Suppose we are writing a function that wraps another\nfunction, and we want our wrapper function to have the same formal arguments\nas the original function, even if it does not use all of them.\nHere is how we can use `alist()` to achieve that:\n\n```r\noriginal_function \u003c- function(a, b, c = 3, d = \"something\") a + b\n\nwrapper_function \u003c- function(...) {\n  # Use the formals of the original function\n  arguments \u003c- match.call(expand.dots = FALSE)$...\n\n  # Update the formals using `alist()`\n  formals(wrapper_function) \u003c- alist(a = , b = , c = 3, d = \"something\")\n\n  # Call the original function\n  do.call(original_function, arguments)\n}\n```\n\nNow, `wrapper_function()` has the same formal arguments as\n`original_function()`, and any arguments passed to `wrapper_function()`\nare forwarded to `original_function()`. This way, even if `wrapper_function()`\ndoes not use all the arguments, it can still accept them, and code that uses\n`wrapper_function()` can be more consistent with code that uses\n`original_function()`.\n\nThe `alist()` function is used here to create a list of formals where\nsome elements are missing, which represents the fact that some arguments\nare required and have no default values. This would not be possible\nwith `list()`, which cannot create lists with missing elements.\n\n### Use internal functions without `:::`\n\nTo use internal functions from packages without using `:::`, you can use\n\n```r\nf \u003c- utils::getFromNamespace(\"f\", ns = \"package\")\nf(...)\n```\n\n## Side-effects\n\n### Return invisibly with `invisible()` for side-effect functions\n\nR functions always return a value. However, some functions are primarily\ndesigned for their side effects. To suppress the automatic printing\nof the returned value, use `invisible()`.\n\n```r\nf \u003c- function(x) {\n  print(x^2)\n  invisible(x)\n}\n```\n\nThe value of `x` can be used later when the result is assigned to a variable\nor piped into the next function.\n\n### Use `on.exit()` for cleanup\n\n`on.exit()` is a useful function for cleaning up side effects, such as\ndeleting temporary files or closing opened connections, even if a function\nexits early due to an error:\n\n```r\nf \u003c- function() {\n  temp_file \u003c- tempfile()\n  on.exit(unlink(temp_file))\n\n  # Do stuff with temp_file\n}\n\nf \u003c- function(file) {\n  con \u003c- file(file, \"r\")\n  on.exit(close(con))\n  readLines(con)\n}\n```\n\nThis function creates a temporary file and then ensures it gets deleted\nwhen the function exits, regardless of why it exits. Note that the arguments\n`add` and `after` in `on.exit()` are important for controlling the overwriting\nand ordering behavior of the expressions.\n\n## Numerical computations\n\n### Create step functions with `stepfun()`\n\nThe `stepfun()` function is an effective tool for creating step functions,\nwhich can be particularly handy in survival analysis.\nFor instance, say we have two survival curves generated from Kaplan-Meier\nestimators, and we want to determine the difference in survival probabilities\nat a given time.\n\nCreate the survival curves using `survfit()`:\n\n```r\nlibrary(\"survival\")\n\nfit_km \u003c- survfit(Surv(stop, event == \"pcm\") ~ 1, data = mgus1, subset = (start == 0))\nfit_cr \u003c- survfit(Surv(stop, event == \"death\") ~ 1, data = mgus1, subset = (start == 0))\n```\n\nConvert these survival curves into step functions:\n\n```r\nstep_km \u003c- stepfun(fit_km$time, c(1, fit_km$surv))\nstep_cr \u003c- stepfun(fit_cr$time, c(1, fit_cr$surv))\n```\n\nWith these step functions, it becomes straightforward to compute the\ndifference in survival probabilities at specific times:\n\n```r\nt \u003c- 1:3 * 1000\nstep_km(t) - step_cr(t)\n```\n\n## Further reading\n\n- [Data Manipulation with R](https://doi.org/10.1007/978-0-387-74731-6)\n- [The R Inferno](https://www.burns-stat.com/documents/books/the-r-inferno/)\n- [stackoverflow: Stack Overflow's Greatest Hits](https://cran.r-project.org/package=stackoverflow)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnanxstats%2Fr-base-shortcuts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnanxstats%2Fr-base-shortcuts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnanxstats%2Fr-base-shortcuts/lists"}