Outline
NA, NULL,
NaN, Inf)R is a programming language for statistical computing and graphics supported by the R Core Team and the R Foundation for Statistical Computing. R is used among data miners, bioinformaticians and statisticians for data analysis and developing statistical software. Users have created packages to augment the functions of the R language.
To work with R, you need to download the program and install it. I recommend that you also install R-Studio. R-Studio is a separate program that makes R easier to use .
Download the latest R installer (.exe) for Windows. Install the downloaded file as any other windows app.
Now that R is installed, you need to download and install RStudio. First download the installer for Windows. Run the installer (.exe file) and follow the instructions.
First download the latest release (“R-version.pkg”) of R Save the .pkg file, double-click it to open, and follow the installation instructions. Now that R is installed, you need to download and install RStudio.
First download the the version for Mac. After downloading, double-click the file to open it, and then drag and drop it to your applications folder.
Note: RStudio the company was renamed Posit in 2022, so current downloads are found at posit.co/download/rstudio-desktop. The desktop application itself is still called “RStudio”. It’s good practice to update both R and RStudio periodically — new R versions are released roughly once a year (typically in April), and packages sometimes require a recent R version to install.
After installation, confirm everything works by checking the version numbers:
# Check your R version
R.version.string
# Check RStudio version (run inside RStudio, from the R console)
rstudioapi::versionInfo()$version
In R, values are stored in variables using the
assignment operator <- (read as “gets”). You can also
use =, but <- is the R convention and is
preferred in scripts because = is reserved for passing
arguments inside function calls.
x <- 10 # preferred style
y = 5 # also works, but less idiomatic
x + y
## [1] 15
# You can also assign left-to-right (less common)
15 -> z
z
## [1] 15
Variable names in R are case-sensitive, must start with a letter (or
a dot not followed by a number), and can contain letters, numbers, dots,
and underscores (e.g. my_var, my.var,
myVar2).
# Arithmetic operators
7 + 3 # addition
## [1] 10
7 - 3 # subtraction
## [1] 4
7 * 3 # multiplication
## [1] 21
7 / 3 # division
## [1] 2.333333
7 %/% 3 # integer division
## [1] 2
7 %% 3 # modulus (remainder)
## [1] 1
7 ^ 3 # exponent
## [1] 343
# Comparison operators (return TRUE/FALSE)
5 == 5 # equal to
## [1] TRUE
5 != 4 # not equal to
## [1] TRUE
5 > 4
## [1] TRUE
5 <= 5
## [1] TRUE
# Logical operators
TRUE & FALSE # element-wise AND
## [1] FALSE
TRUE | FALSE # element-wise OR
## [1] TRUE
!TRUE # NOT
## [1] FALSE
# create create variable "a" that is a vector of one number.
a <- 7
a = 7
a
## [1] 7
# create vectors that consists of multiple numbers.
b <- c(1.25, 2.9, 3.0)
b
## [1] 1.25 2.90 3.00
# create a regular sequence of whole numbers
c <- 1:10
c
## [1] 1 2 3 4 5 6 7 8 9 10
# seq(from, to, by) function ro creat sequence of numbers
d <- seq(1,10,0.5)
d
## [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0
## [16] 8.5 9.0 9.5 10.0
# rep() function to repeat a single number, or a sequence of numbers.
rep(99, times=5)
## [1] 99 99 99 99 99
rep(c(1,2,3), times=5)
## [1] 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
rep(c(1,2,3),each = 5)
## [1] 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3
x <- "Monday"
class(x)
## [1] "character"
y <- c("Mon","Tue","Wed","Thu","Fri","Sat","Sun")
class(y)
## [1] "character"
A factor is a nominal (categorical) variable with a set of known
possible values called levels. They can be created using the
as.factor function.
yy <- as.factor(y)
yy
## [1] Mon Tue Wed Thu Fri Sat Sun
## Levels: Fri Mon Sat Sun Thu Tue Wed
R has several reserved values used to represent missing, undefined, or unbounded data. Understanding the difference matters because they behave differently in calculations.
NA — “Not Available”: a missing value (of any type:
numeric, character, logical…)NULL — represents the absence of a value or an empty
object; it has length 0NaN — “Not a Number”: the result of an undefined
mathematical operation (e.g. 0/0)Inf / -Inf — positive/negative infinity
(e.g. from dividing a number by 0)NA + 1 # any operation with NA returns NA
## [1] NA
is.na(NA) # TRUE
## [1] TRUE
length(NULL) # 0
## [1] 0
0/0 # NaN
## [1] NaN
1/0 # Inf
## [1] Inf
is.nan(0/0)
## [1] TRUE
is.infinite(1/0)
## [1] TRUE
A vector is the most basic data structure in R — even a single number
(like a <- 7 above) is technically a vector of length 1.
Elements of a vector are accessed using square brackets
[ ].
v <- c(10, 20, 30, 40, 50)
v[1] # first element (R indexing starts at 1, not 0)
## [1] 10
v[2:4] # elements 2 through 4
## [1] 20 30 40
v[-1] # all elements except the first
## [1] 20 30 40 50
v[c(1,3)] # elements 1 and 3
## [1] 10 30
v[v > 20] # logical indexing: elements greater than 20
## [1] 30 40 50
length(v) # number of elements
## [1] 5
mat0 <- matrix(NA, ncol=3, nrow=2)
mat0
## [,1] [,2] [,3]
## [1,] NA NA NA
## [2,] NA NA NA
mat1 <- matrix(1:6, ncol=3, nrow=2)
mat1
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
mat2 <- matrix(1:6, ncol=3, nrow=2,byrow = TRUE)
mat2
## [,1] [,2] [,3]
## [1,] 1 2 3
## [2,] 4 5 6
A list is a very flexible container to store data. Each element of a list can contain any type of R object, e.g. a vector, matrix, data.frame, another list, or more complex data types.
lst <- list(d,yy,mat2)
lst
## [[1]]
## [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0
## [16] 8.5 9.0 9.5 10.0
##
## [[2]]
## [1] Mon Tue Wed Thu Fri Sat Sun
## Levels: Fri Mon Sat Sun Thu Tue Wed
##
## [[3]]
## [,1] [,2] [,3]
## [1,] 1 2 3
## [2,] 4 5 6
The data.frame is commonly used for statistical data
analysis in R. It is a special type of list that requires that all
elements (variables) have the same length.
# ToothGrowth is a built-in example dataset (from the datasets package,
# loaded automatically with R) - no need to read a file for it
data(ToothGrowth)
# Print the first 6 rows (the argument is "n", not "row")
head(ToothGrowth, n = 6)
## len supp dose
## 1 4.2 VC 0.5
## 2 11.5 VC 0.5
## 3 7.3 VC 0.5
## 4 5.8 VC 0.5
## 5 6.4 VC 0.5
## 6 10.0 VC 0.5
# Examine data structure of the dataframe
str(ToothGrowth)
## 'data.frame': 60 obs. of 3 variables:
## $ len : num 4.2 11.5 7.3 5.8 6.4 10 11.2 11.2 5.2 7 ...
## $ supp: Factor w/ 2 levels "OJ","VC": 2 2 2 2 2 2 2 2 2 2 ...
## $ dose: num 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 ...
To read a file into R, you need to know the path (directory) to the file and the file’s name. There are two common ways to do this:
setwd() —
points to one exact folder on your computer. This is what many tutorials
show, but it means the code will only work on your computer,
with your specific folder structure and username — it will break for
anyone else (or on a different OS)..Rmd file and your data
file live in the same folder, you can just refer to the file by name,
with no setwd() needed at all. In R Markdown, code chunks
automatically use the folder that contains the .Rmd file as
the working directory. This is the more reproducible approach,
especially if you share the project folder (e.g. via Git or Google
Drive) with someone else.# Option 1: absolute path (edit to match YOUR computer - not portable)
setwd("C:/Users/YourName/Documents/IntroductionToR/")
# Option 2 (recommended): keep the .Rmd and the data file in the same
# folder, or use the here package, which builds paths relative to the
# project's top-level folder regardless of who runs the script or what OS they use
# install.packages("here")
library(here)
dt <- read.csv(here("insurance.csv"), header = TRUE)
#Content: source https://www.kaggle.com/datasets/mirichoi0218/insurance
#age: age of primary beneficiary
#sex: insurance contractor gender, female, male
#bmi: Body mass index, providing an understanding of body, weights that are relatively high or low relative to height,
#children: Number of children covered by health insurance / Number of dependents,
#smoker: Smoking
#region: the beneficiary's residential area in the US, northeast, southeast, southwest, northwest.
#charges: Individual medical costs billed by health insurance
# Read data file and keep it in dt
# (assumes insurance.csv is in the same folder as this .Rmd file)
dt <- read.csv("insurance.csv", header = TRUE)
head(dt) # show the first six rows of the data
## Obs age sex bmi children smoker region charges
## 1 1 19 female 27.90 0 yes southwest NA
## 2 2 18 male 33.77 1 no southeast 1725.552
## 3 3 28 male 33.00 3 no southeast 4449.462
## 4 4 33 male NA 0 no northwest 21984.471
## 5 5 32 male NA 0 no northwest 3866.855
## 6 6 31 female 25.74 0 no southeast 3756.622
str(dt) # show data structure
## 'data.frame': 1338 obs. of 8 variables:
## $ Obs : int 1 2 3 4 5 6 7 8 9 10 ...
## $ age : int 19 18 28 33 32 31 46 37 37 60 ...
## $ sex : chr "female" "male" "male" "male" ...
## $ bmi : num 27.9 33.8 33 NA NA ...
## $ children: int 0 1 3 0 0 0 1 3 2 0 ...
## $ smoker : chr "yes" "no" "no" "no" ...
## $ region : chr "southwest" "southeast" "southeast" "northwest" ...
## $ charges : num NA 1726 4449 21984 3867 ...
# Set data structure
dt$sex <- as.factor(dt$sex)
dt$smoker <- as.factor(dt$smoker)
dt$region <- as.factor(dt$region)
str(dt)
## 'data.frame': 1338 obs. of 8 variables:
## $ Obs : int 1 2 3 4 5 6 7 8 9 10 ...
## $ age : int 19 18 28 33 32 31 46 37 37 60 ...
## $ sex : Factor w/ 2 levels "female","male": 1 2 2 2 2 1 1 1 2 1 ...
## $ bmi : num 27.9 33.8 33 NA NA ...
## $ children: int 0 1 3 0 0 0 1 3 2 0 ...
## $ smoker : Factor w/ 2 levels "no","yes": 2 1 1 1 1 1 1 1 1 1 ...
## $ region : Factor w/ 4 levels "northeast","northwest",..: 4 3 3 2 2 3 3 2 1 2 ...
## $ charges : num NA 1726 4449 21984 3867 ...
Tip:
read.csv()is part of base R and always works, but thereadrpackage’sread_csv()(part of the tidyverse) is faster on large files, guesses column types more reliably, and reads strings as character by default instead of factor. For very messy files, checkread.csv()’ssep,dec,na.strings, andfileEncodingarguments (the latter is useful for files with Thai or other non-ASCII text).
This section uses several packages that don’t ship with base R:
skimr, dplyr, mice,
ggplot2, GGally, and qqplotr.
Each only needs to be installed once per computer with
install.packages(), but must be loaded with
library() in every new R session where you want to
use it.
install.packages(c("skimr", "dplyr", "mice", "ggplot2", "GGally", "qqplotr"))
Calculate descriptive statistics
summary(dt)
## Obs age sex bmi children
## Min. : 1.0 Min. :18.00 female:662 Min. :15.96 Min. :0.000
## 1st Qu.: 335.2 1st Qu.:27.00 male :676 1st Qu.:26.27 1st Qu.:0.000
## Median : 669.5 Median :39.00 Median :30.40 Median :1.000
## Mean : 669.5 Mean :39.21 Mean :30.80 Mean :1.095
## 3rd Qu.:1003.8 3rd Qu.:51.00 3rd Qu.:34.77 3rd Qu.:2.000
## Max. :1338.0 Max. :64.00 Max. :99.00 Max. :5.000
## NAs :14
## smoker region charges
## no :1060 northeast:324 Min. : 1122
## yes: 271 northwest:325 1st Qu.: 4747
## NAs: 7 southeast:364 Median : 9389
## southwest:325 Mean :13286
## 3rd Qu.:16584
## Max. :63770
## NAs :24
# aggregate: compute summary statistics of data subsets
aggregate(charges~smoker, data = dt, FUN=summary)
## smoker charges.Min. charges.1st Qu. charges.Median charges.Mean
## 1 no 1121.874 3996.989 7345.405 8450.999
## 2 yes 12829.455 20984.094 34472.841 32121.864
## charges.3rd Qu. charges.Max.
## 1 11365.285 36910.608
## 2 41034.221 63770.428
aggregate(charges~smoker+region, data = dt, FUN=summary)
## smoker region charges.Min. charges.1st Qu. charges.Median charges.Mean
## 1 no northeast 1694.796 4441.213 8342.909 9200.639
## 2 yes northeast 12829.455 19964.746 28101.333 29883.443
## 3 no northwest 1621.340 4175.355 7239.755 8567.929
## 4 yes northwest 14711.744 21098.554 28868.664 30370.641
## 5 no southeast 1121.874 3486.011 6652.529 8025.767
## 6 yes southeast 16577.780 22853.594 37149.531 34595.779
## 7 no southwest 1241.565 3988.405 7359.957 8036.305
## 8 yes southwest 13844.506 20624.748 35491.640 32623.909
## charges.3rd Qu. charges.Max.
## 1 11938.256 32108.663
## 2 39597.407 58571.074
## 3 11376.680 33471.972
## 4 39983.426 60021.399
## 5 10998.107 36580.282
## 6 43180.856 63770.428
## 7 11044.263 36910.608
## 8 39970.204 52590.829
tmp <- aggregate(cbind(charges,bmi,age)~sex+smoker+region, data = dt, FUN=summary)
# write the output table into .csv file (saved to the working directory)
write.csv(tmp, file = "skim_table.csv")
# Count NA values by column
colSums(is.na(dt))
## Obs age sex bmi children smoker region charges
## 0 0 0 14 0 7 0 24
Skim: an alternative to summary() , quickly providing a
broad overview of a data frame
library(skimr)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
skim(dt)
| Name | dt |
| Number of rows | 1338 |
| Number of columns | 8 |
| _______________________ | |
| Column type frequency: | |
| factor | 3 |
| numeric | 5 |
| ________________________ | |
| Group variables | None |
Variable type: factor
| skim_variable | n_missing | complete_rate | ordered | n_unique | top_counts |
|---|---|---|---|---|---|
| sex | 0 | 1.00 | FALSE | 2 | mal: 676, fem: 662 |
| smoker | 7 | 0.99 | FALSE | 2 | no: 1060, yes: 271 |
| region | 0 | 1.00 | FALSE | 4 | sou: 364, nor: 325, sou: 325, nor: 324 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| Obs | 0 | 1.00 | 669.50 | 386.39 | 1.00 | 335.25 | 669.50 | 1003.75 | 1338.00 | ▇▇▇▇▇ |
| age | 0 | 1.00 | 39.21 | 14.05 | 18.00 | 27.00 | 39.00 | 51.00 | 64.00 | ▇▅▅▆▆ |
| bmi | 14 | 0.99 | 30.80 | 6.90 | 15.96 | 26.27 | 30.40 | 34.77 | 99.00 | ▇▅▁▁▁ |
| children | 0 | 1.00 | 1.09 | 1.21 | 0.00 | 0.00 | 1.00 | 2.00 | 5.00 | ▇▂▂▁▁ |
| charges | 24 | 0.98 | 13285.52 | 12141.46 | 1121.87 | 4746.52 | 9388.75 | 16584.32 | 63770.43 | ▇▂▁▁▁ |
#skimr by categorical variables
dt %>%
dplyr::group_by(sex) %>%
skim()
| Name | Piped data |
| Number of rows | 1338 |
| Number of columns | 8 |
| _______________________ | |
| Column type frequency: | |
| factor | 2 |
| numeric | 5 |
| ________________________ | |
| Group variables | sex |
Variable type: factor
| skim_variable | sex | n_missing | complete_rate | ordered | n_unique | top_counts |
|---|---|---|---|---|---|---|
| smoker | female | 0 | 1.00 | FALSE | 2 | no: 547, yes: 115 |
| smoker | male | 7 | 0.99 | FALSE | 2 | no: 513, yes: 156 |
| region | female | 0 | 1.00 | FALSE | 4 | sou: 175, nor: 164, sou: 162, nor: 161 |
| region | male | 0 | 1.00 | FALSE | 4 | sou: 189, nor: 163, sou: 163, nor: 161 |
Variable type: numeric
| skim_variable | sex | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Obs | female | 0 | 1.00 | 668.05 | 388.70 | 1.00 | 336.25 | 659.50 | 1012.50 | 1338.00 | ▇▇▇▇▇ |
| Obs | male | 0 | 1.00 | 670.92 | 384.40 | 2.00 | 335.00 | 680.50 | 1002.25 | 1334.00 | ▇▇▇▇▇ |
| age | female | 0 | 1.00 | 39.50 | 14.05 | 18.00 | 27.00 | 40.00 | 51.75 | 64.00 | ▇▅▆▆▆ |
| age | male | 0 | 1.00 | 38.92 | 14.05 | 18.00 | 26.00 | 39.00 | 51.00 | 64.00 | ▇▅▅▆▅ |
| bmi | female | 4 | 0.99 | 30.34 | 6.02 | 16.82 | 26.05 | 30.02 | 34.27 | 48.07 | ▂▇▇▅▁ |
| bmi | male | 10 | 0.99 | 31.26 | 7.65 | 15.96 | 26.41 | 30.79 | 35.20 | 99.00 | ▇▅▁▁▁ |
| children | female | 0 | 1.00 | 1.07 | 1.19 | 0.00 | 0.00 | 1.00 | 2.00 | 5.00 | ▇▂▂▁▁ |
| children | male | 0 | 1.00 | 1.12 | 1.22 | 0.00 | 0.00 | 1.00 | 2.00 | 5.00 | ▇▂▂▁▁ |
| charges | female | 11 | 0.98 | 12625.83 | 11181.08 | 1607.51 | 4897.72 | 9432.93 | 14465.16 | 63770.43 | ▇▂▁▁▁ |
| charges | male | 13 | 0.98 | 13933.28 | 12991.61 | 1121.87 | 4591.51 | 9361.33 | 18884.66 | 62592.87 | ▇▂▁▁▁ |
tmp <- dt %>%
dplyr::group_by(smoker,region,sex) %>%
skim(charges,bmi,age)
In R the missing values are coded by the symbol NA.
Sometimes values are stored as 99 that you can convert into NA using the
following command.
dt$bmi[dt$bmi==99] <- NA
#skim(dt)
The function na.omit() returns the object with deletion
of missing values
dt.rmna <- na.omit(dt)
#skim(dt.rmna)
MICE package: linear regression is used to predict
continuous missing values. Logistic regression is used for categorical
missing values. Once this cycle is complete, multiple data sets are
generated. These data sets differ only in imputed missing values.
Generally, it’s considered to be a good practice to build models on
these data sets separately and combining their results.
#install.packages("mice")
library(mice)
## Warning: package 'mice' was built under R version 4.6.1
##
## Attaching package: 'mice'
## The following object is masked from 'package:stats':
##
## filter
## The following objects are masked from 'package:base':
##
## cbind, rbind
dt.imputed<- mice(dt)
##
## iter imp variable
## 1 1 bmi smoker charges
## 1 2 bmi smoker charges
## 1 3 bmi smoker charges
## 1 4 bmi smoker charges
## 1 5 bmi smoker charges
## 2 1 bmi smoker charges
## 2 2 bmi smoker charges
## 2 3 bmi smoker charges
## 2 4 bmi smoker charges
## 2 5 bmi smoker charges
## 3 1 bmi smoker charges
## 3 2 bmi smoker charges
## 3 3 bmi smoker charges
## 3 4 bmi smoker charges
## 3 5 bmi smoker charges
## 4 1 bmi smoker charges
## 4 2 bmi smoker charges
## 4 3 bmi smoker charges
## 4 4 bmi smoker charges
## 4 5 bmi smoker charges
## 5 1 bmi smoker charges
## 5 2 bmi smoker charges
## 5 3 bmi smoker charges
## 5 4 bmi smoker charges
## 5 5 bmi smoker charges
summary(dt.imputed)
## Class: mids
## Number of multiple imputations: 5
## Imputation methods:
## Obs age sex bmi children smoker region charges
## "" "" "" "pmm" "" "logreg" "" "pmm"
## PredictorMatrix:
## Obs age sex bmi children smoker region charges
## Obs 0 1 1 1 1 1 1 1
## age 1 0 1 1 1 1 1 1
## sex 1 1 0 1 1 1 1 1
## bmi 1 1 1 0 1 1 1 1
## children 1 1 1 1 0 1 1 1
## smoker 1 1 1 1 1 0 1 1
# check imputed values
dt.imputed$imp$charges
## 1 2 3 4 5
## 1 26125.675 15359.104 15359.104 14711.744 17085.268
## 7 9863.472 35160.135 13415.038 6289.755 25992.821
## 13 4992.376 5209.579 2632.992 1880.070 5855.903
## 22 5257.508 3956.071 7526.706 1633.044 11737.849
## 23 1748.774 2261.569 3176.816 1972.950 2566.471
## 53 42112.236 39983.426 42112.236 38709.176 38998.546
## 75 8932.084 8232.639 7050.642 6272.477 6548.195
## 92 8615.300 12475.351 11729.680 9447.382 28340.189
## 93 47462.894 46130.526 47291.055 46151.124 45702.022
## 158 32548.340 15006.579 16297.846 14711.744 17085.268
## 211 4133.642 5693.431 2203.472 2257.475 2261.569
## 212 13415.038 8410.047 1163.463 25656.575 7337.748
## 220 21595.382 2198.190 1837.237 1711.027 2457.502
## 305 8944.115 21797.000 36580.282 13607.369 10977.206
## 308 4779.602 3393.356 2899.489 3172.018 2480.979
## 313 63770.428 42856.838 24869.837 48885.136 27533.913
## 314 8527.532 11658.115 9174.136 13012.209 11658.379
## 365 1627.282 3732.625 21595.382 1627.282 1515.345
## 397 7445.918 8551.347 24513.091 11070.535 12629.897
## 431 2566.471 3161.454 2200.831 2156.752 1261.442
## 537 9863.472 5397.617 6986.697 9095.068 9500.573
## 856 1719.436 3268.847 1621.883 1842.519 2974.126
## 895 15161.534 10601.412 10977.206 13887.969 13228.847
## 955 51194.559 20984.094 38126.247 21098.554 37465.344
dt.imputed$imp$bmi
## 1 2 3 4 5
## 4 35.500 33.110 40.480 25.740 24.700
## 5 28.380 30.115 32.300 36.195 30.800
## 9 32.775 27.265 27.600 29.500 26.315
## 43 28.600 32.670 37.070 28.160 26.620
## 44 45.320 32.800 29.370 27.720 45.430
## 130 33.700 36.600 31.600 30.495 29.000
## 147 29.590 41.325 43.010 43.700 36.670
## 423 30.800 36.850 31.800 30.200 36.200
## 480 37.290 33.440 33.330 30.875 33.770
## 496 26.885 28.785 30.590 32.965 25.460
## 531 40.920 31.460 38.060 46.200 35.310
## 542 23.210 39.270 34.800 36.630 28.000
## 543 25.740 41.470 30.690 27.360 30.115
## 544 31.350 33.330 35.310 33.330 31.350
## 545 34.800 18.335 32.700 23.980 42.400
## 546 29.700 19.300 29.070 28.025 27.300
## 547 29.925 19.855 22.705 32.965 23.940
dt.imputed$imp$smoker
## 1 2 3 4 5
## 11 no no no no no
## 20 yes yes yes yes yes
## 54 yes yes yes yes yes
## 61 no no no no no
## 213 no no no no no
## 422 yes yes yes yes yes
## 440 no no no no no
# Since there are 5 imputed data sets, you can select any using complete() function.
dt.completed <- complete(dt.imputed,1)
#str(dt.completed)
# Frequency for one categorical variable
table(dt.completed$smoker)
##
## no yes
## 1064 274
# Contingency table of two categorical variables.
tt <- table(dt.completed[c("sex", "smoker")])
tt
## smoker
## sex no yes
## female 547 115
## male 517 159
# 2 Way Proportion Table
prop.table(tt)
## smoker
## sex no yes
## female 0.40881913 0.08594918
## male 0.38639761 0.11883408
# Add margin totals to your table.
addmargins(tt)
## smoker
## sex no yes Sum
## female 547 115 662
## male 517 159 676
## Sum 1064 274 1338
# Three-ways frequency table
ftable(dt.completed$region, dt.completed$sex, dt.completed$smoker)
## no yes
##
## northeast female 132 29
## male 125 38
## northwest female 135 29
## male 132 29
## southeast female 139 36
## male 134 55
## southwest female 141 21
## male 126 37
A bar chart shows the count (or proportion) of observations
in each category of a categorical variable. Use it to answer
questions like “how many/what share of the sample falls into each
group?” and, with a second variable added via fill=, “how
does that breakdown change across another category?”
#Install package 'ggplot2'
library(ggplot2)
#Aesthetics tell ggplot what should be on the x-axis, what should be on the y-axis, and what the colors should be.
ggplot(dt.completed,aes(x = smoker)) +
geom_bar()
# Add label
# Note: ..count.. is old ggplot2 syntax, still works but is superseded by
# after_stat(count) in ggplot2 >= 3.3.0; both are shown below
ggplot(dt.completed,aes(x = smoker)) +
geom_bar()+
geom_label(aes(x= smoker,label=..count..),
stat='count', color="black")
## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(count)` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Modern equivalent of the plot above
ggplot(dt.completed,aes(x = smoker)) +
geom_bar()+
geom_label(aes(x= smoker,label=after_stat(count)),
stat='count', color="black")
# Change the order of items
ggplot(dt.completed,aes(x = smoker)) +
geom_bar()+
geom_label(aes(x= smoker,label=..count..),
stat='count', color="black")+
scale_x_discrete(limits=c("yes", "no"))
#Using color= or fill= to refer to a categorical variable (called a “discrete” variable in ggplot) allows you to separate the shape by that category.
# Stacked bars
ggplot(dt.completed,aes(x = smoker,fill=sex)) +
geom_bar(position = "stack")
# Dodged bars
ggplot(dt.completed,aes(x = smoker,fill=sex)) +
geom_bar(position = "dodge")
# Filled bars
ggplot(dt.completed,aes(x = smoker,fill=sex)) +
geom_bar(position="fill")+
scale_fill_manual(values = c("#009999", "darkblue"))
How to read it: the height of each bar is the count
in that category — taller means more observations. The three
fill=sex versions each answer a slightly different
question:
smoker group while still showing the sex
split, but hard to compare sex proportions across bars
because the bar segments don’t share a common baseline.sex within each
smoker group directly.fill position would make that imbalance obvious
in a way the stacked version hides.GGally helps to produces a matrix of scatter plots for visualizing the correlation between variables.
#install.packages("GGally")
library(GGally)
## Warning: package 'GGally' was built under R version 4.6.1
ggpairs(dt.completed[,c(2,4,5,8)], title="correlogram with ggpairs()")
# ggcorr: correlation matrixes
ggcorr(dt.completed[,c(2,4,5,8)], label = TRUE, label_size = 4, label_round = 4)
# ggpairs split by group (smoker)
ggpairs(dt.completed, columns = c(2,4,5,8), ggplot2::aes(colour=smoker))
How to read it:
ggpairs() grid — each row/column is
one numeric variable. The diagonal shows that
variable’s own distribution (a density curve); the lower
triangle shows pairwise scatterplots; the upper
triangle shows the numeric correlation coefficient for that
pair (closer to ±1 = stronger linear relationship, closer to 0 = little
to no linear relationship, sign tells you the direction). This is a fast
way to screen many variable pairs at once before picking specific ones
to model or plot in detail.ggcorr() heatmap — the same
correlation coefficients as color-coded tiles, typically dark/warm for
strong positive correlation, cool for strong negative, and
near-white/grey for close to zero. Scan for the darkest tiles first —
those are the pairs worth investigating further with a scatterplot.smoker — a correlation
that looks weak overall can be much stronger within a subgroup
(or vice versa). If bmi and charges correlate
more strongly among smokers than non-smokers, that’s evidence the
relationship between BMI and medical cost depends on smoking status — a
clue that an interaction effect might matter in a later model.A data display that shows the relationship between two numerical variables.
# Scatterplot between two quantitative variables
ggplot(dt.completed,aes(x=bmi,y=charges))+
geom_point()+
theme_bw()
# Shaped by sex
ggplot(dt.completed,aes(x=bmi,y=charges))+
geom_point(aes(shape=sex))+
theme_bw()
# Colored by sex
ggplot(dt.completed,aes(x=bmi,y=charges))+
geom_point(aes(col=sex))+
theme_bw()
# Change the point size by children
ggplot(dt.completed,aes(x=bmi,y=charges))+
geom_point(aes(col=sex,size=children))+
theme_bw()
# Facet wrap to create multiple plots by categorical variable(s)
ggplot(dt.completed,aes(x=bmi,y=charges))+
geom_point(aes(col=sex))+
theme_bw()+
facet_wrap(region ~ .)
# Grid by region and smoker
ggplot(dt.completed,aes(x=bmi,y=charges))+
geom_point(aes(col=sex))+
theme_bw()+
facet_grid(cols = vars(region),rows=vars(smoker))+
theme(legend.position="top")#change the position of label
# Grid by region and sex
pl1 <- ggplot(dt.completed,aes(x=bmi,y=charges))+
geom_point(aes(col=smoker))+
theme_bw()+
facet_grid(cols = vars(region),rows=vars(sex))+
theme(legend.position="top")
pl1
# Add regression lines
pl1+ geom_smooth(method=lm, aes(fill=smoker))
## `geom_smooth()` using formula = 'y ~ x'
How to read it: each point is one observation,
placed by its bmi (x) and charges (y). Three
things to look for:
geom_smooth(method=lm)) is only an appropriate summary if
the underlying trend actually looks straight.charges vs bmi for the whole sample looks like
a messy cloud with a weak trend, but once you color by
smoker, two distinct bands typically separate out —
non-smokers cluster at lower charges regardless of BMI, while smokers’
charges climb steeply with BMI. That’s a classic case where an overall
correlation understates what’s really going on: the relationship
between BMI and cost is conditional on smoking status, not a
single global trend. Faceting by region on top of that lets
you check whether this pattern holds consistently across regions or is
driven by one area.geom_smooth(method=lm) adds a fitted
regression line with a shaded 95% confidence band; a narrow band means
the line is estimated precisely, a wide band means more uncertainty
(often because there are fewer points in that facet).A standardized way of displaying the distribution of numerical data based on a five number summary (min, first quartile (Q1), median, third quartile (Q3), and max)
# Boxplot of charges by sex
ggplot(dt.completed,aes(x=sex,y=charges))+
geom_boxplot()+
theme_bw()
# Colored by smoker
ggplot(dt.completed,aes(x=sex,y=charges))+
geom_boxplot(aes(col=smoker))+
coord_flip()+
theme_bw()
# Grided by region
p <- ggplot(dt.completed,aes(x=sex,y=charges,fill=smoker))+
geom_boxplot()+
theme_bw()+
facet_grid(cols = vars(region))
# Box plot with dot plot; visualize the data on boxplot
p + geom_point(position=position_jitterdodge(),alpha=0.3) +
theme_bw()
How to read it: the box spans the interquartile range (IQR) — the middle 50% of the data, from Q1 to Q3 — with a line at the median. The whiskers typically extend to the most extreme point within 1.5×IQR of the box, and any points beyond that are plotted individually as potential outliers.
charges by smoker here, the smokers’ box
usually sits noticeably higher than the non-smokers’ box, meaning the
typical smoker pays substantially more, not just a few extreme
cases.region lets you check
whether the smoker/non-smoker gap in charges is consistent everywhere,
or whether one region drives it.A graph that shows the frequency of numerical data using rectangles.
# Basic histogram
ggplot(dt.completed, aes(x=charges)) +
geom_histogram()+
theme_bw()
## `stat_bin()` using `bins = 30`. Pick better value `binwidth`.
# Add mean line
ggplot(dt.completed, aes(x=charges)) +
geom_histogram()+
geom_vline(aes(xintercept=mean(charges)),color="blue",
linetype="dashed", size=1)+
theme_bw()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `stat_bin()` using `bins = 30`. Pick better value `binwidth`.
# Histogram plot colors by groups
ggplot(dt.completed, aes(x=charges, fill=smoker,color=smoker)) +
geom_histogram( alpha=0.5, position="identity")+
theme_bw()
## `stat_bin()` using `bins = 30`. Pick better value `binwidth`.
# Combine histogram and density plots :
# (as noted earlier, ..density.. can also be written as after_stat(density))
ggplot(dt.completed, aes(x=charges, fill=smoker,color=smoker)) +
geom_histogram(aes(y=..density..),alpha=0.5, position="identity")+
geom_density(alpha=0.6)+
theme_bw()
## `stat_bin()` using `bins = 30`. Pick better value `binwidth`.
ggplot(dt.completed, aes(x=charges, fill=smoker,color=smoker)) +
geom_histogram(aes(y=..density..),alpha=0.5, position="identity")+
geom_density(alpha=0.6)+
theme_bw()+
facet_grid(cols = vars(region),rows=vars(sex))
## `stat_bin()` using `bins = 30`. Pick better value `binwidth`.
How to read it: the x-axis is binned values of
charges, and bar height is how many observations fall in
each bin — so the overall shape of the bars is the
distribution.
charges
is a good example of a right-skewed (positively skewed)
distribution: a tall cluster of relatively low values with a long tail
stretching toward high values. This matters practically because it means
the mean (the dashed blue line) sits noticeably to the right of most
people’s actual costs — a small number of very expensive cases pull the
average up, so the mean alone would overstate what a “typical” person
pays. This is exactly why the median (visible on the boxplot above) is
often a more honest summary for skewed cost/income-type data than the
mean.smoker typically
reveals this: what looked like one skewed distribution overall resolves
into two separate, more coherent shapes — non-smokers clustered at lower
charges, smokers shifted higher — which is the same story the boxplot
and scatterplot sections tell from different angles.geom_density) is a smoothed version of the same shape,
useful for comparing groups of different sizes since it’s rescaled to
have equal area regardless of sample size, whereas raw histogram bar
heights depend on how many observations are in each group.A normal probability plot is used to check if the given data set is
normally distributed or not. It is used to compare a data set with the
normal distribution. we use the provided stat_qq_ functions
to construct a complete Q-Q plot with the points, reference line, and
the confidence bands. As default, the standard Q-Q Normal plot with
Normal confidence bands is constructed:
#install.package(qqplotr)
#library(ggplot2)
library(qqplotr)
## Warning: package 'qqplotr' was built under R version 4.6.1
##
## Attaching package: 'qqplotr'
## The following objects are masked from 'package:ggplot2':
##
## stat_qq_line, StatQqLine
ggplot(dt.completed, aes(sample = charges))+
stat_qq_point()+
stat_qq_line()+
labs(x = "Theoretical Quantiles", y = "Sample Quantiles")+
theme_bw()
ggplot(dt.completed, aes(sample = charges))+
stat_qq_point()+
stat_qq_line()+
labs(x = "Theoretical Quantiles", y = "Sample Quantiles")+
theme_bw()+
facet_grid(cols = vars(region),rows=vars(smoker))
How to read it: each point compares one observed value’s quantile against the quantile it would have if the data were perfectly normally distributed. If the data really is approximately normal, the points fall closely along the diagonal reference line.
charges is right-skewed.
Seeing that same skew confirmed in the Q-Q plot is a useful cross-check
between the two.log(charges)) before fitting a linear model, since
many standard statistical methods lean on a normality assumption that a
skewed variable like raw charges violates. The
facet_grid version lets you check whether that skew is
present in every region/smoker subgroup or concentrated in a few.?mean # opens the help page for a function
help(mean) # same as above
??"linear model" # searches help pages for a phrase
example(mean) # runs the worked examples from the help page
vignette(package = "dplyr") # lists long-form guides bundled with a package
When something errors, copy the exact error message into a search engine along with the package/function name — R’s error messages are usually specific enough that someone else has hit (and solved) the same issue.
ggplot2,
dplyr, R Markdown, and more.Rproj) so working directories and file paths stay
predictable and portable, as discussed in Section 4.sessionInfo(), so results can be reproduced later even if
packages are updated:sessionInfo()
## R version 4.6.0 (2026-04-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=English_United States.utf8
## [2] LC_CTYPE=English_United States.utf8
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C
## [5] LC_TIME=English_United States.utf8
##
## time zone: Asia/Bangkok
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] qqplotr_0.0.7 GGally_2.4.0 ggplot2_4.0.3 mice_3.19.0 dplyr_1.2.1
## [6] skimr_2.2.2
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 shape_1.4.6.1 xfun_0.57
## [4] bslib_0.11.0 twosamples_2.0.1 caTools_1.18.4
## [7] lattice_0.22-9 bitops_1.1-0 vctrs_0.7.3
## [10] tools_4.6.0 Rdpack_2.6.6 generics_0.1.4
## [13] parallel_4.6.0 pbmcapply_1.5.1 tibble_3.3.1
## [16] DEoptimR_1.2-0 pan_2.0 pkgconfig_2.0.3
## [19] jomo_2.7-6 Matrix_1.7-5 RColorBrewer_1.1-3
## [22] S7_0.2.2 lifecycle_1.0.5 compiler_4.6.0
## [25] farver_2.1.2 stringr_1.6.0 repr_1.1.7
## [28] codetools_0.2-20 htmltools_0.5.9 sass_0.4.10
## [31] yaml_2.3.12 glmnet_5.0 pracma_2.4.6
## [34] pillar_1.11.1 nloptr_2.2.1 jquerylib_0.1.4
## [37] tidyr_1.3.2 MASS_7.3-65 cachem_1.1.0
## [40] reformulas_0.4.4 iterators_1.0.14 rpart_4.1.27
## [43] boot_1.3-32 foreach_1.5.2 mitml_0.4-5
## [46] robustbase_0.99-7 nlme_3.1-169 ggstats_0.13.0
## [49] tidyselect_1.2.1 digest_0.6.39 stringi_1.8.7
## [52] purrr_1.2.2 labeling_0.4.3 splines_4.6.0
## [55] fastmap_1.2.0 grid_4.6.0 cli_3.6.6
## [58] qqconf_1.3.2 magrittr_2.0.5 base64enc_0.1-6
## [61] survival_3.8-6 broom_1.0.13 withr_3.0.2
## [64] opdisDownsampling_1.6 scales_1.4.0 backports_1.5.1
## [67] rmarkdown_2.31 nnet_7.3-20 lme4_2.0-1
## [70] evaluate_1.0.5 knitr_1.51 rbibutils_2.4.1
## [73] doParallel_1.0.17 mgcv_1.9-4 rlang_1.2.0
## [76] Rcpp_1.1.1-1.1 glue_1.8.1 rstudioapi_0.18.0
## [79] minqa_1.2.8 jsonlite_2.0.0 R6_2.6.1
renv package, which
snapshots the exact package versions used so the project can be rebuilt
identically on another machine.