QSURE Coding Workshop 2025

Part 1: R Basics


Karissa Whiting
June 10th, 2025

Purpose of Coding Workshop

Provide you tools & resources to get properly set up to conduct impactful and reproducible research during your time at MSK (and after!)


  • Beginners: Introduce you to general coding concepts and basics of the R programming language

  • Intermediate/Advanced Coders: Fill potential gaps in your R knowledge and introduce packages useful for common statistical analyses and reporting.

  • All: Learn to build reproducible code bases (of any language) from the ground up.

Workshop Agenda

  • Lesson 1 – 6/10/2025
    • Overview of Coding Concepts
    • R Basics (Quick Review)
  • Lesson 2 - 6/12/2025
    • Guided Example
      • Project Setup & Reproducibility
      • Data Cleaning
      • Analyzing/Modeling the Data
      • Reporting Your Results
  • Lesson 3 - TBD

R, Rstudio, Open source philosophy

  • R is an open-source programming language used mostly for statistical computing and graphics. R is an object-oriented language, with a functional style.

  • Open source means the original source code is freely available, and can be distributed and modified. Also, users can contribute to the usefulness by creating packages, which implement specialized functions for all kinds of uses (e.g. statistical methods, graphical capabilities, reporting tools). Added Bonus: vibrant R community!

  • RStudio is an integrated development environment (IDE) for R. It includes a console, syntax-highlighting code editor that supports direct code execution, as well as tools for plotting, history, debugging and work space management.

Python

  • Python is a versatile programming language used widely in data science, web development, automation, and statistics. Unlike R, it was created as a general purpose language but has evolved significantly.

  • Python is also Open source (PyPI - Python Package Index)

  • Some popular IDEs include VS Code, JupyterLab, Positron and more

R vs. Python

Which is better?

R vs. Python

They’re both great!

R Basics

General Things

  • <- is the assignment operator (= also works)
  • R is case sensitive, bE cArEfUl!
  • ? is your friend if you want to look at documentation! (e.g. type ?mean() in the console)

A Note About The Pipe Operator

  • The %>% (pipe) is a useful way to link functions together to make your code more succinct and easier to read.

  • |> (base pipe) is another way to pipe operations

Data Types

R basic data types:

  • logical (TRUE)
  • integer (1)
  • numeric (a.k.a. double) (1.2)
  • character ("Purple")
  • factor (“a”)
  • complex (nobody ever uses these really)

Beware Data Type Coercion

What is the most flexible data type?

Beware Data Type Coercion

  • Since columns of a data.frame must be of the same type, some data may be coerced in unexpected ways when reading in a csv or excel file.

  • Character type is often the default for mixed data types

What will happen when we try to add these?

How Data is Stored

R has 5 basic data structures:

  1. vector
  2. matrix
  3. array
  4. list
  5. data.frame/tibble

How Data is Stored

1. vector

  • only 1 data type allowed
# character
c("apple", "orange")

[1] “apple” “orange”

# numeric
c(1:15)

[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

How Data is Stored

  1. vector

2. matrix

2d, only 1 data type allowed

letters <- c("a","b","c","d", "e", "f")
matrix(letters, nrow=2, ncol=3)
     [,1] [,2] [,3]
[1,] "a"  "c"  "e" 
[2,] "b"  "d"  "f" 

How Data is Stored

  1. vector
  2. matrix

3. array

  • n-dimensions, of 1 data type
# Create two vectors of different lengths.
vector1 <- c(5,9,3)
vector2 <- c(10,11,12,13,14,15)

array(c(vector1,vector2),dim = c(3,3,2))
, , 1

     [,1] [,2] [,3]
[1,]    5   10   13
[2,]    9   11   14
[3,]    3   12   15

, , 2

     [,1] [,2] [,3]
[1,]    5   10   13
[2,]    9   11   14
[3,]    3   12   15

How Data is Stored

  1. vector
  2. matrix
  3. array

4. list

  • Any data type allowed
  • Most flexible (often used for output of functions)
my_list <- list("a", 2, TRUE) 
str(my_list)
List of 3
 $ : chr "a"
 $ : num 2
 $ : logi TRUE

How Data is Stored

  1. vector
  2. matrix
  3. array
  4. list

5. data.frame/tibble

- Any data type is allowed, BUT each column has to have the SAME type
- Most important for data analysts. Most similar to an excel spreadsheet/statistical data file
head(iris, 4)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa

The R Analysis Workflow

The Analysis Workflow

Steps of a basic data analysis project:


1. Setup Your Project

2. Clean and Explore Data

3. Analyze Data

4. Report Your Findings

5. Iterate, Share, and Collaborate!

Analysis Workflow Tools (R)

1. Setup Your Project

- `here`, `renv`

2. Clean and Explore Data

- `tidyverse`

3. Analyze Data

- `stats`
- `survival`, `lme4`, `glmnet`
- `ggplot2` 

4. Report Your Findings

- R Markdown / Quarto
- `gt` / `gtsummary`

5. Iterate, Share, and Collaborate!

- git / GitHub

Analysis Workflow Tools (Python)

1. Setup Your Project

- `venv`, `conda`

2. Clean and Explore Data

- `pandas` / `numpy`

3. Analyze Data

- `scipy.stats`, `statsmodels`, `scikit-learn`
- `seaborn`, `matplotlib`

4. Report Your Findings

- Jupyter / Quarto

5. Iterate, Share, and Collaborate!

- git / GitHub

The Analysis Workflow

Steps of a basic data analysis project:


1. Setup Your Project

  1. Clean and Explore Data

  2. Analyze Data

  3. Report Your Findings

  4. Iterate, Share, and Collaborate!

Anatomy of a Project

  • keep raw and processed data separate (raw-data, vs. data)

  • folder for scripts ordered or labelled descriptively

  • optionally can have admin for project notes, etc and outputs for final reports and figures

  • README - text file that introduces and explains a project (usethis::use_readme_md())

  • R Project (.Rproj file) - tells RStudio all your files belong to one project and sets working directory for entire project.

Your Turn: Setup Your Project

  1. Create a new folder on your computer and name it “your-initials-case-study-2025”

  2. Create subfolders within your project folder called: admin, raw-data, scripts, data, outputs

  3. Create a new R Project in Rstudio from this folder (File > New Project > Existing Directory)

  4. Create a README.md using usethis::use_readme_md()

  5. Open a new R file (RStudio > New File > R Script) to use as a scratch file

The Analysis Workflow


Steps of a basic data analysis project:

  1. Setup Your Project

2. Clean and Explore Data

  1. Analyze Data

  2. Report Your Findings

  3. Iterate, Share, and Collaborate!

Exploring Your Data

  • colnames() - will give you the column names
  • ncol() and nrow() - will give you the total count of columns and rows respectively
  • class(), str(), attributes() will give you meta-information on the object
  • head(), tail() show the top or bottom rows of your df
  • View() will show the whole dataframe
  • table() will summarise variables

Exploring Your Data

Try these out:

Cleaning Data: Intro to tidyverse

The tidyverse package is a collection of R packages designed for data analysis, all of which share a similar design, grammar, and structure.

# load it
library(tidyverse)

# check out the cute logo
tidyverse_logo()
⬢ __  _    __   .    ⬡           ⬢  . 
 / /_(_)__/ /_ ___  _____ _______ ___ 
/ __/ / _  / // / |/ / -_) __(_-</ -_)
\__/_/\_,_/\_, /|___/\__/_/ /___/\__/ 
     ⬢  . /___/      ⬡      .       ⬢ 

Cleaning Data: Intro to tidyverse

  • readr: data import/export
  • tibble: easier to work with data frames
  • dplyr: data manipulation
  • tidyr: data manipulation
  • ggplot2: graphics and visualization
  • purrr: functional programming toolkit, replaces the need for many loops
  • stringr: string manipulation
  • forcats: re-imagined factor data types

There are several additional packages which are installed as part of the tidyverse, but are not loaded by default.

The tidyverse style

  • Overall the tidyverse style emphasizes code readability and intuitive coding

  • Human-readable syntax and pipe-based workflows

  • Shortcuts for common data manipulation tasks

  • tidyverse has been developed and significantly improved in the last few years, with a lot of ongoing work being done to further increase usability.

Cleaning Data: dplyr

The dplyr package is a data manipulation and cleaning package. A few of the key functions (verbs) in dplyr are:

  • select()
  • mutate()
  • filter()
  • arrange()
  • group_by()
  • summarize()

All take a data frame as input, and return a data frame as output.

The Analysis Workflow

Steps of a basic data analysis project:


  1. Setup Your Project

  2. Clean and Explore Data

3. Analyze Data

  1. Report Your Findings

  2. Iterate, Share, and Collaborate!

Statistical Models

We will cover:

  • linear model

  • logistic model

  • survival analyses

  • and more….(depending on what’s useful for you!)

Formula Syntax

  • Code for models and stats tests often require formula syntax
  • In general ~ is used to separate your outcome on the left hand side and your predictors on the right hand side
  • Your outcome will always be on the left side of the ~
  • Only some univariate tests like chisq.test() do not use the ~ notation
  • The stats package is already loaded in R which will make it easier to use common statistical tests
  • General notation: model(outcome ~ covariates, data)

Example of linear model

  • Continuous outcome
  • Specifying interactions
mtcars$vs  <- as.character(mtcars$vs)
mtcars$cyl   <- as.character(mtcars$cyl)
mod1 <- lm(mpg ~ vs * cyl, data = mtcars)
class(mod1) # class of lm which is a list

[1] “lm”

names(mod1)

[1] “coefficients” “residuals” “effects” “rank”
[5] “fitted.values” “assign” “qr” “df.residual”
[9] “contrasts” “xlevels” “call” “terms”
[13] “model”

Example cont.

summary(mod1)

Call:
lm(formula = mpg ~ vs * cyl, data = mtcars)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.3300 -1.4437  0.0875  1.5250  7.1700 

Coefficients: (1 not defined because of singularities)
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   26.000      3.318   7.836    2e-08 ***
vs1            0.730      3.480   0.210  0.83541    
cyl6          -5.433      3.831  -1.418  0.16757    
cyl8         -10.900      3.434  -3.174  0.00374 ** 
vs1:cyl6      -2.172      4.305  -0.504  0.61801    
vs1:cyl8          NA         NA      NA       NA    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 3.318 on 27 degrees of freedom
Multiple R-squared:  0.7361,    Adjusted R-squared:  0.697 
F-statistic: 18.82 on 4 and 27 DF,  p-value: 1.696e-07

Check Model Diagnostics

  • All models have different underlying assumptions (e.g. normality of residuals). Consider these when choosing a model and check them when model fitting.
  • Check multicollinearity among your variables and how your models handles it:

Check Model Diagnostics

  • Check outliers and influential points (e.g. Cook’s Distance- a measure of how influential a data point is in a regression analysis).

(If the Cook’s Distance of a data point exceeds this cutoff, that data point might be considered unusually influential)

Check model diagnositcs

#many options for which! 
plot(mod1, which=1, cook.levels=cutoff)

Example cont.

summary(mod1)

Call:
lm(formula = mpg ~ vs * cyl, data = mtcars)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.3300 -1.4437  0.0875  1.5250  7.1700 

Coefficients: (1 not defined because of singularities)
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   26.000      3.318   7.836    2e-08 ***
vs1            0.730      3.480   0.210  0.83541    
cyl6          -5.433      3.831  -1.418  0.16757    
cyl8         -10.900      3.434  -3.174  0.00374 ** 
vs1:cyl6      -2.172      4.305  -0.504  0.61801    
vs1:cyl8          NA         NA      NA       NA    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 3.318 on 27 degrees of freedom
Multiple R-squared:  0.7361,    Adjusted R-squared:  0.697 
F-statistic: 18.82 on 4 and 27 DF,  p-value: 1.696e-07
  • while it is nice to see the summary results, you wouldn’t present them in this fashion

Report Your Findings

  • broom and gt/gtsummary will help
  • broom is a package that helps tidy model results into data.frames
  • this helps with reporting and you can further format the data.frame and present with gt
moddf <- broom::tidy(mod1) %>% #didn't load broom just called one function 
          mutate(p.value = round(p.value,3)) %>% 
          select(-std.error)

gt::gt(moddf)
term estimate statistic p.value
(Intercept) 26.000000 7.8364568 0.000
vs1 0.730000 0.2097843 0.835
cyl6 -5.433333 -1.4182193 0.168
cyl8 -10.900000 -3.1738857 0.004
vs1:cyl6 -2.171667 -0.5044923 0.618
vs1:cyl8 NA NA NA

gtsummary

  • The {gtsummary} package provides an elegant and flexible way to create publication-ready analytical and summary tables u
  • The {gtsummary} package summarizes data sets, regression models, and more, using sensible defaults with highly customizable capabilities.
  • For helpful examples see package website

gtsummary

tbl_regression(mod1)
Characteristic Beta 95% CI1 p-value
vs


    0
    1 0.73 -6.4, 7.9 0.8
cyl


    4
    6 -5.4 -13, 2.4 0.2
    8 -11 -18, -3.9 0.004
vs * cyl


    1 * 6 -2.2 -11, 6.7 0.6
    1 * 8


1 CI = Confidence Interval

Customizing gtsummary

There are tons of ways to customize your basic {gtsummary} outputs.

Example of adding labels:

library(labelled)

var_label(mtcars$cyl) <- "Cylinder"

mod_summary <- lm(mpg ~ vs * cyl, data = mtcars) %>%
  tbl_regression() %>% 
  bold_labels() %>% 
  modify_caption("New title for model")

Customizing gtsummary

mod_summary
New title for model
Characteristic Beta 95% CI1 p-value
vs


    0
    1 0.73 -6.4, 7.9 0.8
Cylinder


    4
    6 -5.4 -13, 2.4 0.2
    8 -11 -18, -3.9 0.004
vs * Cylinder


    1 * 6 -2.2 -11, 6.7 0.6
    1 * 8


1 CI = Confidence Interval

Logistic models

  • Binary outcome (0/1)
  • R will model the ‘1’ as the event by default make sure your variable is coded correctly
mtcars$vs <- as.numeric(mtcars$vs)
mtcars$am <- as.character(mtcars$am)

mod2 <- glm(vs ~ am , data = mtcars, family =  "binomial")

Summarize logistic model

tbl_regression(mod2, exponentiate = TRUE) %>% 
    bold_labels()
Characteristic OR1 95% CI1 p-value
am


    0
    1 2.00 0.48, 8.76 0.3
1 OR = Odds Ratio, CI = Confidence Interval

Survival analysis

  • Outcome is both time and an event (e.g death, progression)
  • Must specify both time and event outcome variables in the left side of model formula
  • Dr. Emily Zabor from Cleveland Clinic put together great materials for survival analysis: Link to Materials

Survival analysis

library(survival)

lung <- lung %>% 
        mutate(ph.ecog = as.character(ph.ecog),
               sex = as.character(sex))

mod3 <- coxph(Surv(time, status)~ph.ecog+sex,data = lung)
mod4 <- survfit(Surv(time, status) ~ sex,data = lung)

Survival analysis

tbl_regression(mod3, exponentiate = TRUE)
Characteristic HR1 95% CI1 p-value
ph.ecog


    0
    1 1.52 1.03, 2.25 0.036
    2 2.58 1.66, 4.01 <0.001
    3 7.76 1.04, 58.0 0.046
sex


    1
    2 0.58 0.42, 0.81 0.001
1 HR = Hazard Ratio, CI = Confidence Interval

Survival analysis: Model Assumptions

  • Cox models assume proportional hazards

  • Test this assumptions:

cox.zph(mod3)
        chisq df     p
ph.ecog  5.64  3 0.130
sex      2.56  1 0.110
GLOBAL   7.83  4 0.098

Thursday: Coding Exercise

  • Coding Case Study: Diabetes Risk Factors
  • Materials coming soon

Thank You!