library(tidyverse) # readr, dplyr, ggplot2, etc.
library(janitor) # clean_names()Data Skills for Psychology — Module 5
These slides are adapted from Applied Data Skills Ch. 4 and Appendix D: Data Types.
By the end of this class, you will be able to:
Make sure you have these packages loaded:
Tip
You will probably need to install the janitor package before you try to load it. In the Console, run:
install.packages(janitor)
You will only need to do this once!
Every value in R has a type. Knowing the type tells you what you can do with it.
You can’t take the average of someone’s name, and you can’t capitalize a number — the type determines what operations make sense.
The three basic types: numeric, character, and logical.
Numbers are stored as either doubles (decimals) or integers (whole numbers):
Characters (also called “strings”) are text wrapped in quotes:
Single or double quotes both work:
Warning
"10" and 10 are not the same thing. One is text, the other is a number.
Logical values are TRUE or FALSE — the result of yes/no questions:
Note
You might see T and F as shortcuts, but always spell out TRUE and FALSE to avoid confusion.
Two useful functions for inspecting types:
| Function | What it tells you |
|---|---|
typeof() |
The low-level storage type |
class() |
The high-level category |
is.numeric() |
Is it a number? (TRUE/FALSE) |
is.character() |
Is it text? |
is.logical() |
Is it TRUE/FALSE? |
For most purposes, class() is the one you’ll use most often.
A factor is a special type for categorical data. It stores a fixed set of possible values (called levels) in a specific order.
Why use factors? Because they control the order that categories appear in plots and tables.
We’ll use factors when we import data later today, to control how delivery types and age groups are ordered.
Dates and times are stored as numbers with a special class:
R stores dates as the number of days since January 1, 1970. The class tells R to display them as readable dates instead of raw numbers.
We won’t work much with dates today, but it’s good to know they exist!
A vector holds multiple values of the same type. Create one with c():
Warning
R will silently coerce mixed types to the most flexible type (usually character). This can cause sneaky bugs!
Use square brackets [] to pull out elements by position:
A list can hold different types of data in one container:
Tip
We are primarily going to use a special kind of list: tibbles (a type of data frame).
A table (or tibble) is the most important container for data analysis. It’s a list of columns where every column has the same length:
Try it!
pets with three columns: name (character), species (character), and age (numeric). Include at least 3 rows.glimpse() to check the column types.$ to extract just the name column.Often when we work with data, we want to load it from a separate file, such as a csv (comma-separated value) file.
Use read_csv() from the tidyverse to load data:
Always check the loading summary! Look for:
Do you remember the other methods we have for exploring data?
Try it!
spec() and glimpse() — how many rows and columns?If a column has no useful info, skip it at load time using col_skip:
This is cleaner than loading everything and dropping columns later.
You can control the order of categorical variables right from the start, by defining them as a factor and setting their levels:
# Again, here we're telling R about the data columns BEFORE we load the data
birth_cols <- cols(
SIMDQuintileQF = col_skip(),
AgeGroup = col_factor(
levels = c("Under 25", "25-34",
"35 and over", "Unknown")),
Induced = col_factor(
levels = c("Not Induced", "Induced", "Unknown"))
)
# Now reload
births <- read_csv("data/12.1_delivery.csv",
col_types = birth_cols)Why bother? Because plots and tables will display groups in this order.
Column names with mixed case are annoying. The janitor package fixes this:
clean_names() converts everything to lowercase with underscores: AgeGroup becomes age_group.
Good habit
Run clean_names() every time you import a new dataset!
$ Operator and Pipes$The $ lets you pull out a single column from a tibble or data frame:
. . .
Translate |> as “and then”:
The pipe sends the result of each step as the first argument to the next function.
Note
You’ll see two pipes in the wild: |> (base R, newer) and %>% (tidyverse/magrittr, older). They work almost identically. We’ll use |>.
Rewrite this using pipes
Try rewriting this as a single piped chain!
For now, don’t worry too much about what each step does - pay attention to the way that the variables are passed from one line to the next.
count() Functioncount() tallies up rows per group:
You can count combinations of columns:
Note
count() counts rows, not births! The livebirths column contains the actual numbers per row. To get total births, you need a different function that we’ll go over shortly: summarise() with sum().
Use mutate() to add a percentage column:
Breaking it into two steps (divide, then round) is cleaner than nesting: round(100 * n / sum(n)). But either is technically correct.
Try these
age_groupdelivery and age_group togetherWhich delivery type is most common in the data?
summarize() Functionsummarize() (or sometimes, summarise()) computes summary statistics and returns a new, smaller table:
Common summary functions:
| Function | What it does |
|---|---|
n() |
Count rows |
sum() |
Add up values |
mean() |
Average |
median() |
Middle value |
min(), max() |
Extremes |
quantile() |
Percentiles |
If you get NA from a summary function, it usually means missing data:
This is logical: the average of 1 + 2 + “I don’t know” isn’t 1.5 — it’s “I don’t know.”
group_by() + summarize()You can group the data before you summarize it:
Warning
Always call ungroup() when you’re done! Forgetting this causes mysterious bugs later.
Answer these questions with code
arrange()arrange() sorts your results:
Use desc() for descending order (largest first).
Group by multiple variables:
Try it
Reverse the order of delivery and financial_year in group_by(). How does the output change?
Combine everything into one clean chain:
births_per_year_type_age <- births |>
mutate(
year = str_extract(financial_year, ".{4}"),
year = as.integer(year)
) |>
filter(age_group != "Unknown",
delivery != "Not Known") |>
summarise(
n = sum(livebirths),
.by = c(year, delivery, age_group)
) |>
mutate(
pcnt = n / sum(n) * 100,
.by = c(year, age_group)
)
head(births_per_year_type_age)From raw data to publication-quality plot — all in one reproducible pipeline!
read_csv() loads data; use col_skip() and col_factor() to clean on importclean_names() standardizes column names (always do this!)|> (pipe) chains functions together — read it as “and then”count() tallies rows per groupsummarise() computes stats like sum(), mean(), n()group_by() computes summaries per groupmutate() adds or modifies columns (e.g., percentages)ungroup() after group_by()!📖 Read: Chapter 4 of Applied Data Skills to solidify today’s material
✍️ Assignment: You will practice these skills using a real psychology dataset. See the website for more information.
❓ Questions?