install.packages(c("devtools", "roxygen2", "usethis", "gitcreds"))Building Your Own R Package
From GitHub to a Fully Functioning Package
Overview
In this tutorial we are going to build a real, installable R package from scratch and host it on GitHub. By the end of class you will have your own package that anyone can install directly from GitHub with a single line of code.
Here is the plan for today:
- Get GitHub and RStudio talking to each other
- Install a finished example package so you can see where we are heading
- Build your own package from scratch
- Minihack — write your own 3 functions and publish them as a package on GitHub
Before We Start
Work through these three things before we do anything else. They are not exciting but if you skip them you will hit walls later.
Step 0.1 — Install the required packages
Run this in your R console:
Step 0.2 — Create a GitHub account and verify your email
Go to github.com and sign up for a free account if you do not have one already.
You must verify your GitHub email before you can create repositories. Check your inbox for the verification email and click the link before moving on. This takes a few minutes.
Step 0.3 — Set up your Personal Access Token (PAT)
GitHub stopped accepting regular passwords in 2021. Instead, RStudio uses a Personal Access Token to authenticate with GitHub. Think of it as a special password just for RStudio. You only have to do this once per machine.
a. Run this in your R console:
usethis::create_github_token()This should open GitHub in your browser automatically. If it does not, go here manually: https://github.com/settings/tokens/new
b. On the GitHub page that opens, fill in the settings like this:
| Field | What to do |
|---|---|
| Note | Type anything descriptive like rstudio or my laptop |
| Expiration | Set to 90 days |
| Select scopes | Make sure repo is checked leave everything else as is |
Scroll to the bottom and click Generate token.
Copy the token immediately. It starts with ghp_ and looks like a long string of random characters. Once you navigate away from this page the token is gone forever and you will have to generate a new one. Copy it before doing anything else.
c. Go back to RStudio and run this:
gitcreds::gitcreds_set()It will prompt you to enter your token. Paste it in and hit Enter.
When you paste the token you will not see anything appear on screen — the field is hidden for security just like a password prompt. This is normal. Paste it and hit Enter even though it looks like nothing happened.
d. Confirm it worked by running:
gitcreds::gitcreds_get()If it returns your GitHub username without an error you are good to go. If it throws an error go back to step a and try again.
If you ever get an authentication error when pushing in the future it usually means your token expired. Go to github.com/settings/tokens, generate a new one, and run gitcreds::gitcreds_set() again.
Part 1 — GitHub Setup
We are starting on GitHub, not in R. The reason is simple: if you create the repo on GitHub first, RStudio can connect to it automatically without any command line setup.
Step 1.1 — Create a new repository
Once logged in to GitHub:
a. Click the + icon in the top right corner of the page
b. Click New repository
c. Fill in these settings:
| Setting | What to do |
|---|---|
| Repository name | Something short, lowercase, no spaces — like mypackage or LASTNAMEverse |
| Visibility | Public — must be public or install won’t work |
| Add a README file | Check this box — do not skip it |
| .gitignore template | None |
| License | None — we will add this from R later |
d. Click Create repository
Package name rules — R package names cannot start with a number, cannot contain underscores or spaces, and can only use letters, numbers, and dots. mypackage is fine. my_package is not. 2coolpackage is not.
The README box must be checked. It initializes the repo with a commit and without it the repo is completely empty and RStudio will have trouble cloning it.
Step 1.2 — Copy the repo URL
a. Click the green Code button on your new repo page
b. Make sure HTTPS is selected at the top — not SSH
c. Copy the URL — it looks like https://github.com/yourusername/mypackage.git
Leave this browser tab open. You will paste this URL into RStudio in the next step.
Part 2 — Clone into RStudio
We are switching to RStudio now.
Step 2.1 — Clone the repo
a. Go to File → New Project
b. Click Version Control
c. Click Git
d. Fill in the dialog: Repository URL: paste your GitHub URL here
Project directory name: leave it, it auto-fills from the URL
Create project as subdirectory of: click Browse and pick somewhere easy to find like a folder on your Desktop, I would also highly recommend saving this file in the same root directory so you can reopen it later.
Two things to avoid when picking your save location:
Nested projects — do not save inside a folder that already has an .Rproj file in it. RStudio will warn you. If you see that warning, click Cancel and pick a different folder.
OneDrive or iCloud — do not save in a synced folder. These services try to sync files at the same time Git is managing them and they conflict with each other constantly.
IMPORTANT: when you click create projet, r studio will close this window to open the PACKAGE RSTUDIO window. You will need to reopen this file in a new R studio window to contninue follwoing along. Pay attention to the rest of this file because there will be some code that needs to go into the PACKAGE RSTUDIO window that will open from clicking create project. I recomend saving this file in the root directory that your eventual
e. Click Create Project
RStudio will clone the repo and reopen. You should now see a Git tab appear in the top right panel next to Environment and History. That means the connection to GitHub worked.
IMPORTANT
If RStudio asks “New project would be nested inside an existing project — do you want to create anyway?” and shows numbered options like 1: Absolutely not, 2: Nope, 3: AgreeThis means that r found another r proj ni a previous directory and is trying to nest them. Hit No and start this step over. I think the esasiest thing to do is save a folder on your desktop to put this that way there is no chance of this nesting thing happening.
Step 2.2 — Turn the folder into an R package
Copy and Run this in the PACKAGE RSTUDIO R console. The "." means turn this current folder into a package — it will not create a new nested subfolder.
library(devtools)
create_package(".")Two things will happen and both are expected:
a. RStudio will ask if you want to overwrite the existing .Rproj file — click Yes
b. RStudio will then automatically reopen the project
Let it do its thing.
Step 2.3 — Check the file structure
After create_package(".") finishes, look at your Files tab in the bottom right panel. You should see:
mypackage/
├── DESCRIPTION <- package metadata, we fill this in next
├── NAMESPACE <- leave this alone forever, roxygen2 manages it
├── R/ <- all your functions go in here
├── README.md <- already here from GitHub
└── mypackage.Rproj
Never edit the NAMESPACE file by hand. roxygen2 writes it automatically every time you run document(). If you edit it manually you will break things and the errors will be confusing.
Step 2.4 — Fill in DESCRIPTION
a. Click on DESCRIPTION in the Files tab to open it. Update every field with your own information:
Package: mypackage
Title: What My Package Does
Version: 0.0.1
Authors@R: person("First", "Last", email = "you@email.com",
role = c("aut", "cre"))
Description: A short paragraph about what the package does.
License: MIT + file LICENSE
Encoding: UTF-8
b. Save it
c. Add a license file by running this in the PACKAGE RSTUDIO console:
usethis::use_mit_license("Your Name")The aut role means author and cre means maintainer. You need at least one person with cre or the package will throw a warning.
Part 3 — See the Finished Product First
Before we build anything, let’s install a finished example package so you can see what we are working toward.
Step 3.1 — Install the example package
Run this in THIS RSTUDIO console:
devtools::install_github("rgerladmonkman/monkverse")This installs a package directly from GitHub — no CRAN needed. This is exactly how your classmates will install your package at the end of today.
This takes 30-60 seconds. You will see a lot of output scroll by in the console that is totally normal. Wait for it to finish and return the > prompt before moving on.
Step 3.2 — Load it and try the functions
library(monkverse)
# A simple greeting
greet("RYAN")
# Calculate z-scores for a vector
z_score(c(2, 4, 4, 4, 5, 5, 7, 9))
# Cohen's d effect size between two groups
cohen_d(c(1, 2, 3, 4, 5), c(3, 4, 5, 6, 7))
# A Yu-Gi-Oh duel
duel_outcome("Blue-Eyes White Dragon", 3000, "Dark Magician", 2500)Step 3.3 — Check the help pages
Run these one at a time:
?greet
?z_score
?cohen_d
?duel_outcomeEach one opens a proper help page — just like ?mean or ?lm. This is what we are building today. By the end of class your functions will have help pages too.
Part 4 — Write Your Functions
All functions live in .R files inside the R/ folder. One function per file is the convention — it keeps things organized and is considered best practice.
Step 4.1 — Function 1: greet (simple)
a. In the PACKAGE RSTUDIO Go to File → New File → R Script
b. Before typing anything, save it: File → Save As
c. Navigate into the R/ folder inside your project, name it greet.R, and click Save
Make sure you are saving inside the R/ folder — not just anywhere in the project. If it is not in R/ the package will not find it.
d. Type this in the rscript for greet.r:
# Dont copy line above jsut lines below
greet <- function(name, loud = FALSE) {
msg <- paste0("Hello, ", name, "! Welcome to my package!")
if (loud) toupper(msg) else msg
}e. Save it, then test it in PACKAGE RSTUDIO window:
library(devtools)
load_all()
greet("RYAN")
greet("RYAN", loud = TRUE)load_all() is your main development tool. It simulates installing the package without actually installing it. Every time you change a function, run load_all() again and you can test the updated version immediately. Get used to running it constantly.
If you get could not find function "load_all" it means you forgot to run library(devtools) first so always have devtools on hand as well.
Step 4.2 — Function 2: z_score (medium)
a. In the PACKAGE RSTUDIO Go to File → New File → R Script
b. Go to File → New File → R Script, save it as R/z_score.R
c. Type this in the file:
z_score <- function(x) {
(x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}c. Save it, then test it PACKAGE RSTUDIO window:
load_all()
z_score(c(2, 4, 4, 4, 5, 5, 7, 9))The na.rm = TRUE inside mean() and sd() means missing values get ignored automatically instead of returning NA. That is an important detail for real data.
Step 4.3 — Function 3: cohen_d (complex)
a. a. In the PACKAGE RSTUDIO Go to File → New File → R Script
b. Go to File → New File → R Script, save it as R/cohen_d.R
c. Type this in the file:
cohen_d <- function(group1, group2) {
pooled_sd <- sqrt((sd(group1, na.rm = TRUE)^2 + sd(group2, na.rm = TRUE)^2) / 2)
(mean(group1, na.rm = TRUE) - mean(group2, na.rm = TRUE)) / pooled_sd
}c. Save it, then test it PACKAGE RSTUDIO window:
load_all()
cohen_d(c(1, 2, 3, 4, 5), c(3, 4, 5, 6, 7))Cohen’s d is the standardized difference between two group means. A value of -2 here means the groups are very far apart — 2 standard deviations difference.
Step 4.4 — Function 4: duel_outcome (complex and fun)
a. a. In the PACKAGE RSTUDIO Go to File → New File → R Script
b. Go to File → New File → R Script, save it as R/duel_outcome.R
b. Type this in the file:
duel_outcome <- function(name1, atk1, name2, atk2) {
attacks <- c(
"Blue-Eyes White Dragon" = "White Lightning",
"Dark Magician" = "Dark Magic Attack",
"Exodia the Forbidden One" = "Obliterate",
"Red-Eyes Black Dragon" = "Inferno Fire Blast"
)
attack1 <- ifelse(name1 %in% names(attacks), attacks[name1], "attacks")
attack2 <- ifelse(name2 %in% names(attacks), attacks[name2], "attacks")
if (atk1 > atk2) {
cat(name1, "attacks with", attack1, "!\n")
cat(name2, "is destroyed!", name1, "deals", atk1 - atk2, "damage to your opponent.\n")
} else if (atk2 > atk1) {
cat(name2, "attacks with", attack2, "!\n")
cat(name1, "is destroyed!", name2, "deals", atk2 - atk1, "damage to your opponent.\n")
} else {
cat(name1, "attacks with", attack1, "!\n")
cat(name2, "attacks with", attack2, "!\n")
cat("Both monsters are destroyed! No damage dealt.\n")
}
}c. Save it, then test it in PACKAGE RSTUDIO window:
library(devtools)
load_all()
duel_outcome("Blue-Eyes White Dragon", 3000, "Dark Magician", 2500)
duel_outcome("Dark Magician", 2500, "Dark Magician", 2500)This combination of packages shows you can add any functions you really want to a package!
Part 5 — Document with roxygen2
Right now your functions work but they have no documentation. If you run ?greet you get nothing. That is about to change.
roxygen2 lets you write documentation as special comments directly above your function using #' (hash apostrophe). When you run document(), roxygen2 reads those comments and automatically generates proper help files — the same kind you see when you run ?mean.
Step 5.1 — Insert the roxygen2 skeleton
a. Open greet.R
b. Click your cursor on any line inside the function body — between the opening { and closing }. Click on the msg <- line to be safe.
The cursor must be inside the curly braces. If it is on the greet <- function(...) line or below the closing } the shortcut will not work.
c. Insert the skeleton using the keyboard shortcut:
| Mac | Windows / Linux |
|---|---|
Cmd + Option + Shift + R |
Ctrl + Alt + Shift + R |
Or from the menu: Code → Insert Roxygen Skeleton
Or my favorite option copy and paste this below:
You should see this appear above your function:
#' Title
#'
#' @param name
#' @param loud
#'
#' @return
#' @export
#'
#' @examplesroxygen2 reads your function arguments and creates a @param line for each one automatically. That is why name and loud appeared without you typing them.
Step 5.2 — What each tag means
| Tag | What it does |
|---|---|
#' |
Marks a roxygen2 comment — note the apostrophe after the hash |
First line after #' |
The title of the help page |
@param |
Documents one argument — one line per argument |
@return |
Describes what the function gives back to the user |
@export |
Makes the function available to users after they install the package |
@examples |
Runnable code that shows up in the help page |
@export is the most important tag. Without it, your function exists inside the package but users cannot call it after installing. This is the single most common mistake. Every function you want users to be able to call needs @export.
Step 5.3 — Fill in the skeleton for all four functions
greet.R:
#' Greet someone by name
#'
#' @param name A character string. The name to greet.
#' @param loud Logical. Should the greeting be uppercase? Default is FALSE.
#'
#' @return A character string containing the greeting.
#' @export
#'
#' @examples
#' greet("Ada")
#' greet("Ada", loud = TRUE)
greet <- function(name, loud = FALSE) {
msg <- paste0("Hello, ", name, "! Welcome to my package!")
if (loud) toupper(msg) else msg
}z_score.R:
#' Calculate z-scores
#'
#' @param x A numeric vector.
#'
#' @return A numeric vector of z-scores with the same length as x.
#' @export
#'
#' @examples
#' z_score(c(1, 2, 3, 4, 5))
#' z_score(c(2, 4, 4, 4, 5, 5, 7, 9))
z_score <- function(x) {
(x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}cohen_d.R:
#' Calculate Cohen's d effect size
#'
#' @param group1 A numeric vector for the first group.
#' @param group2 A numeric vector for the second group.
#'
#' @return A single numeric value representing Cohen's d.
#' @export
#'
#' @examples
#' cohen_d(c(1, 2, 3, 4, 5), c(3, 4, 5, 6, 7))
cohen_d <- function(group1, group2) {
pooled_sd <- sqrt((sd(group1, na.rm = TRUE)^2 + sd(group2, na.rm = TRUE)^2) / 2)
(mean(group1, na.rm = TRUE) - mean(group2, na.rm = TRUE)) / pooled_sd
}duel_outcome.R:
#' Simulate a Yu-Gi-Oh duel outcome
#'
#' @param name1 A character string. Name of the first monster.
#' @param atk1 Numeric. Attack points of the first monster.
#' @param name2 A character string. Name of the second monster.
#' @param atk2 Numeric. Attack points of the second monster.
#'
#' @return Prints the duel outcome to the console.
#' @export
#'
#' @examples
#' duel_outcome("Blue-Eyes White Dragon", 3000, "Dark Magician", 2500)
duel_outcome <- function(name1, atk1, name2, atk2) {
attacks <- c(
"Blue-Eyes White Dragon" = "White Lightning",
"Dark Magician" = "Dark Magic Attack",
"Exodia the Forbidden One" = "Obliterate",
"Red-Eyes Black Dragon" = "Inferno Fire Blast"
)
attack1 <- ifelse(name1 %in% names(attacks), attacks[name1], "attacks")
attack2 <- ifelse(name2 %in% names(attacks), attacks[name2], "attacks")
if (atk1 > atk2) {
cat(name1, "attacks with", attack1, "!\n")
cat(name2, "is destroyed!", name1, "deals", atk1 - atk2, "damage to your opponent.\n")
} else if (atk2 > atk1) {
cat(name2, "attacks with", attack2, "!\n")
cat(name1, "is destroyed!", name2, "deals", atk2 - atk1, "damage to your opponent.\n")
} else {
cat(name1, "attacks with", attack1, "!\n")
cat(name2, "attacks with", attack2, "!\n")
cat("Both monsters are destroyed! No damage dealt.\n")
}
}Step 5.4 — Generate the documentation
Once all four files have their roxygen2 comments filled in run in PACKAGE RSTUDIO window :
document()You should see:
Updating mypackage documentation
Writing NAMESPACE
Writing greet.Rd
Writing z_score.Rd
Writing cohen_d.Rd
Writing duel_outcome.Rd
Look at your Files tab — a new man/ folder appeared. Click into it and you can see the .Rd files that roxygen2 wrote automatically. You never have to touch those files directly.
Now reload and check that the help pages work:
load_all()
?greet
?z_score
?cohen_d
?duel_outcomeAlways run document() before load_all(). If you run them in the wrong order, load_all() will load stale documentation and ?yourfunction will show nothing. The correct order is always: document() then load_all().
Part 6 — Add a Dataset to Your Package
Packages can ship with built-in datasets — just like how ggplot2 comes with mpg and diamonds. We are going to add a Yu-Gi-Oh! card dataset so there is real data to run the functions on.
Step 6.1 — Set up the data-raw folder
Run this in the PACKAGE RSTUDIO window console:
usethis::use_data_raw("yugioh_cards")This creates a data-raw/ folder and opens a new script called yugioh_cards.R inside it. The data-raw/ folder is where you keep the scripts that build your datasets — it is not part of the installed package, just your behind-the-scenes workspace.
Step 6.2 — Paste in the dataset script
a. Delete everything in the script that just opened
b. Replace it with this:
yugioh_cards <- data.frame(
name = c(
"Blue-Eyes White Dragon", "Dark Magician", "Exodia the Forbidden One",
"Red-Eyes Black Dragon", "Summoned Skull", "Celtic Guardian",
"Kuriboh", "Beaver Warrior", "Gaia the Fierce Knight", "Curse of Dragon",
"Dark Magician Girl", "Buster Blader", "Obelisk the Tormentor",
"Slifer the Sky Dragon", "The Winged Dragon of Ra", "Saggi the Dark Clown",
"Mystical Elf", "Wall of Illusion", "Man-Eater Bug", "Harpie Lady",
"Harpie Lady Sisters", "Magician of Faith", "Witch of the Black Forest",
"Sangan", "Jinzo", "Barrel Dragon", "Gemini Elf", "La Jinn the Mystical Genie",
"Luster Dragon", "Exodia Necros", "Gate Guardian", "Launcher Spider",
"Sword Hunter", "Insect Queen", "Perfectly Ultimate Great Moth",
"Thousand-Eyes Restrict", "Relinquished", "Dark Ruler Ha Des",
"Yata-Garasu", "Injection Fairy Lily", "Vampire Lord", "Fiber Jar",
"Chaos Emperor Dragon", "Black Luster Soldier", "Dark Magician of Chaos",
"Skilled Dark Magician", "Breaker the Magical Warrior", "Tribe-Infecting Virus",
"Sinister Serpent", "Morphing Jar",
"Mirror Force", "Pot of Greed", "Change of Heart", "Monster Reborn",
"Dark Hole", "Raigeki", "Harpie's Feather Duster", "Heavy Storm",
"Mystical Space Typhoon", "Premature Burial"
),
card_type = c(
rep("Monster", 50),
rep("Spell", 7),
"Spell", "Spell", "Spell"
),
attribute = c(
"LIGHT", "DARK", "DARK", "DARK", "DARK", "EARTH", "DARK", "EARTH",
"EARTH", "DARK", "DARK", "EARTH", "DIVINE", "DIVINE", "DIVINE",
"DARK", "LIGHT", "DARK", "EARTH", "WIND", "WIND", "LIGHT", "DARK",
"DARK", "DARK", "DARK", "WIND", "WIND", "WIND", "DARK", "EARTH",
"FIRE", "EARTH", "EARTH", "EARTH", "LIGHT", "DARK", "DARK", "WIND",
"LIGHT", "DARK", "EARTH", "DARK", "EARTH", "DARK", "DARK", "EARTH",
"WATER", "WATER", "EARTH",
rep(NA, 10)
),
monster_type = c(
"Dragon", "Spellcaster", "Spellcaster", "Dragon", "Fiend", "Warrior",
"Fiend", "Beast-Warrior", "Warrior", "Dragon", "Spellcaster", "Warrior",
"Fairy", "Divine-Beast", "Divine-Beast", "Fiend", "Spellcaster", "Illusion",
"Insect", "Winged Beast", "Winged Beast", "Spellcaster", "Spellcaster",
"Fiend", "Machine", "Machine", "Spellcaster", "Fiend", "Dragon", "Spellcaster",
"Warrior", "Machine", "Warrior", "Insect", "Insect", "Spellcaster",
"Spellcaster", "Fiend", "Winged Beast", "Fairy", "Zombie", "Plant",
"Dragon", "Warrior", "Spellcaster", "Spellcaster", "Warrior", "Aqua",
"Reptile", "Rock",
rep(NA, 10)
),
atk = c(
3000, 2500, 1000, 2400, 2500, 1400, 300, 1200, 2300, 2000,
2000, 2600, 4000, 4000, 4000, 600, 800, 1000, 450, 1300,
1950, 300, 1100, 1000, 2400, 2600, 1900, 1800, 1900, 3000,
3750, 2200, 2950, 2200, 3500, 0, 0, 2450, 200, 400,
2000, 0, 3000, 3000, 2800, 1900, 1600, 2300, 300, 700,
rep(NA, 10)
),
def = c(
2500, 2100, 1000, 2000, 1200, 1200, 200, 700, 2100, 1500,
1700, 2300, 4000, 4000, 4000, 1900, 2000, 1800, 600, 1400,
2600, 200, 1200, 600, 1500, 1900, 1600, 1500, 1600, 3000,
3000, 2400, 2200, 2400, 3000, 4000, 0, 1600, 100, 300,
2000, 0, 2500, 2500, 2600, 1400, 1000, 0, 300, 600,
rep(NA, 10)
),
level = c(
8, 7, 3, 7, 6, 4, 1, 4, 7, 5,
6, 7, 10, 10, 10, 3, 4, 4, 2, 4,
6, 2, 4, 3, 6, 5, 4, 4, 4, 10,
11, 5, 5, 7, 8, 1, 1, 6, 2, 3,
6, 4, 8, 8, 8, 4, 4, 4, 1, 2,
rep(NA, 10)
),
rarity = c(
"Ultra Rare", "Ultra Rare", "Ultra Rare", "Super Rare", "Rare",
"Common", "Common", "Common", "Rare", "Rare",
"Ultra Rare", "Ultra Rare", "Ultra Rare", "Ultra Rare", "Ultra Rare",
"Common", "Common", "Common", "Common", "Rare",
"Rare", "Common", "Rare", "Common", "Ultra Rare",
"Rare", "Rare", "Common", "Common", "Ultra Rare",
"Ultra Rare", "Rare", "Rare", "Rare", "Ultra Rare",
"Ultra Rare", "Ultra Rare", "Ultra Rare", "Common", "Rare",
"Rare", "Rare", "Ultra Rare", "Ultra Rare", "Ultra Rare",
"Rare", "Rare", "Rare", "Common", "Rare",
"Ultra Rare", "Ultra Rare", "Ultra Rare", "Ultra Rare", "Ultra Rare",
"Ultra Rare", "Ultra Rare", "Rare", "Common", "Rare"
),
power_rating = c(
5, 5, 5, 4, 4, 2, 1, 1, 3, 3,
4, 4, 5, 5, 5, 1, 2, 2, 2, 3,
3, 2, 3, 2, 4, 4, 3, 3, 3, 5,
5, 3, 4, 4, 5, 4, 4, 5, 4, 4,
4, 3, 5, 5, 5, 3, 4, 4, 1, 3,
5, 5, 5, 5, 5, 5, 5, 4, 3, 4
),
is_banned = c(
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE,
FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE,
FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE
),
stringsAsFactors = FALSE
)
usethis::use_data(yugioh_cards, overwrite = TRUE)c. Save the file
d. Run the whole script by clicking Source in the top right of the editor (or Cmd/Ctrl + Shift + Enter)
You should see a new data/ folder appear in the Files tab containing yugioh_cards.rda. That is the dataset bundled into the package.
Step 6.3 — Document the dataset
Datasets need documentation just like functions.
a. Create a new R Script and save it as R/yugioh_cards.R
b. Paste this in:
#' Yu-Gi-Oh! Cards Dataset
#'
#' A dataset containing 60 iconic Yu-Gi-Oh! cards from the original series,
#' including monster stats, card attributes, rarity, and competitive status.
#'
#' @format A data frame with 60 rows and 10 columns:
#' \describe{
#' \item{name}{Character. The name of the card.}
#' \item{card_type}{Character. Either "Monster" or "Spell".}
#' \item{attribute}{Character. The card attribute (DARK, LIGHT, EARTH, WIND, FIRE, WATER, DIVINE). NA for Spell cards.}
#' \item{monster_type}{Character. The monster type (Dragon, Spellcaster, Warrior, etc.). NA for Spell cards.}
#' \item{atk}{Numeric. Attack points of the monster. NA for Spell cards.}
#' \item{def}{Numeric. Defense points of the monster. NA for Spell cards.}
#' \item{level}{Numeric. The level of the monster (1-11). NA for Spell cards.}
#' \item{rarity}{Character. Card rarity (Common, Rare, Super Rare, Ultra Rare).}
#' \item{power_rating}{Numeric. Community power rating on a 1-5 scale (1 = weak, 5 = extremely powerful).}
#' \item{is_banned}{Logical. Whether the card is on the competitive forbidden list.}
#' }
#'
#' @examples
#' head(yugioh_cards)
#'
#' # Use with z_score()
#' z_score(yugioh_cards$atk[!is.na(yugioh_cards$atk)])
#'
#' # Use with cohen_d() - compare ATK of Dragons vs Warriors
#' monsters <- yugioh_cards[yugioh_cards$card_type == "Monster", ]
#' dragons <- monsters[monsters$monster_type == "Dragon", ]
#' warriors <- monsters[monsters$monster_type == "Warrior", ]
#' cohen_d(dragons$atk, warriors$atk)
#'
#' # Use with duel_outcome()
#' duel_outcome("Blue-Eyes White Dragon", 3000, "Dark Magician", 2500)
"yugioh_cards"c. Save it
Step 6.4 — Re-run document() and test the dataset
a. Run in RPACKAGE WINDOW
document()
load_all()b. Check the dataset loaded correctly:
head(yugioh_cards)c. Try the functions on real data:
# z-scores of all monster attack points
z_score(yugioh_cards$atk[!is.na(yugioh_cards$atk)])
# Summary stats for attack points
describe_var(yugioh_cards$atk)
# How many cards of each rarity?
freq_table(yugioh_cards$rarity)
# Are Dragons stronger than Warriors on average?
monsters <- yugioh_cards[yugioh_cards$card_type == "Monster", ]
dragons <- monsters[monsters$monster_type == "Dragon", ]
warriors <- monsters[monsters$monster_type == "Warrior", ]
cohen_d(dragons$atk, warriors$atk)
# Check the help page
?yugioh_cardsNow we have multiple functions and a built in default data set as part of our package!
Part 7 — Push to GitHub
We are starting in RStudio then briefly switching to the browser.
Step 7.1 — Commit your files
a. In PACKAGE RSTUDIO Click the Git tab in the top right panel. You should see all your new files listed — DESCRIPTION, NAMESPACE, R/greet.R, R/z_score.R, R/cohen_d.R, R/duel_outcome.R, R/yugioh_cards.R, the man/ folder, and the data/ folder.
- If you do not see the git button in the top right in the files window: navigate back to the rproj and reopen it. From the menu on the left select GIT/SVN then toggle version control system to GIT. Let R restart studio and then the GIT option should appear.
b. Check the box next to every file in the list
c. Click Commit
d. A new window opens — type a commit message in the top right box:
add four functions, yugioh dataset, and roxygen2 documentation
e. Click the Commit button
You should see a message saying the commit was successful. Close that window.
Step 7.2 — Push to GitHub
a. Back in the Git tab, click the green Push arrow
b. If it asks for a password and rejects it, your PAT token may not have saved correctly. Go back to Step 0.3 and repeat the token setup, then try pushing again.
Step 7.3 — Confirm it worked
a. Go to your GitHub repo page in the browser — https://github.com/yourusername/mypackage — and refresh it
b. You should now see all your files: DESCRIPTION, NAMESPACE, R/, man/, data/, LICENSE, and README.md
Step 7.4 — Test the install line
a. Run this in the console to confirm the package installs cleanly from GitHub:
devtools::install_github("myusername/mypackage")
library(pracpackage)
greet("YOUR NAME HERE")
#Turn to a class mate and see if you can load there package!
devtools::install_github("classmate/classmatepackage")
library(pracpackage)
greet("YOUR NAME HERE")If that works, you just published a real R package!
Minihack 1
Only one minihack but it is a mulit-parter!
Now it is your turn to build your own package from scratch.
The task
- Create a new GitHub repo and clone it into RStudio
- Run
create_package(".")and fill inDESCRIPTION - Write 3 original functions one per file in the
R/folder - Add roxygen2 documentation to every function using the skeleton shortcut
- Run
document()thenload_all()and make sure all 3 functions work - Commit everything and push to GitHub
- Submit: your
devtools::install_github("yourusername/yourpackage")line to the course portal
Your functions need to
- Have at least one argument each
- Do something — even something simple
- Have complete roxygen2 docs: title,
@param,@return,@export, and@examples
Checklist before submitting
Swap install_github() lines with a classmate and install each other’s packages. Call one of their functions. Does it work as expected?
Quick Reference
| What you want to do | Code to run |
|---|---|
| Create a package from the current folder | create_package(".") |
| Load package into memory for testing | load_all() |
| Generate help files from roxygen2 comments | document() |
| Add a license file | usethis::use_mit_license("Your Name") |
| Set up GitHub token | usethis::create_github_token() |
| Save GitHub token | gitcreds::gitcreds_set() |
| Install a package from GitHub | devtools::install_github("user/repo") |
| Open a help page | ?function_name |
Common Errors
could not find function "load_all" Run library(devtools) first.
is not a valid package name Package names cannot start with a number or contain underscores or spaces. Rename it letters, numbers, and dots only.
Roxygen skeleton shortcut does nothing Your cursor is not inside the function body. Click on a line between the { and } and try again.
Push rejected with “fetch first” Click Pull first, then Push.
GitHub password rejected on push Your PAT token may have expired. Go to Step 0.3 and generate a new one.
Function installs but can’t be called You forgot @export in the roxygen2 comments. Add it and re-run document().
?myfunction shows nothing after document() You ran load_all() before document(). Always run document() first.
NAMESPACE errors after document() You edited the NAMESPACE file by hand. Delete it and run document() again to regenerate it cleanly.
Rstudio will get Github will commit but not push: You may have a typo somewhere and then are not connected