library(dplyr)
library(readr)
library(haven)
library(ggplot2)
9 Objects, Functions, Loops
Where are we? Where are we headed?
Up till now, you should have covered:
- R basic programming
- Data Import
- Statistical Summaries
- Visualization
Today we’ll cover
- Objects
- Functions
- Loops
9.1 What is an object?
Now that we have covered some hands-on ways to use graphics, let’s go into some fundamentals of the R language.
Let’s first set up
<- read_csv("data/input/usc2010_001percent.csv", col_types = cols()) cen10
Objects are abstract symbols in which you store data. Here we will create an object from copy
, and assign cen10
to it.
<- cen10 copy
This looks the same as the original dataset:
copy
# A tibble: 30,871 × 4
state sex age race
<chr> <chr> <dbl> <chr>
1 New York Female 8 White
2 Ohio Male 24 White
3 Nevada Male 37 White
4 Michigan Female 12 White
5 Maryland Female 18 Black/Negro
6 New Hampshire Male 50 White
7 Iowa Female 51 White
8 Missouri Female 41 White
9 New Jersey Male 62 White
10 California Male 25 White
# ℹ 30,861 more rows
What happens if you do this next?
<- "" copy
It got reassigned:
copy
[1] ""
9.1.1 lists
Lists are one of the most generic and flexible type of object. You can make an empty list by the function list()
<- list()
my_list my_list
list()
And start filling it in. Slots on the list are invoked by double square brackets [[]]
1]] <- "contents of the first slot -- this is a string"
my_list[["slot 2"]] <- "contents of slot named slot 2"
my_list[[ my_list
[[1]]
[1] "contents of the first slot -- this is a string"
$`slot 2`
[1] "contents of slot named slot 2"
each slot can be anything. What are we doing here? We are defining the 1st slot of the list my_list
to be a vector c(1, 2, 3, 4, 5)
1]] <- c(1, 2, 3, 4, 5)
my_list[[ my_list
[[1]]
[1] 1 2 3 4 5
$`slot 2`
[1] "contents of slot named slot 2"
You can even make nested lists. Let’s say we want the 1st slot of the list to be another list of three elements.
1]][[1]] <- "subitem 1 in slot 1 of my_list"
my_list[[1]][[2]] <- "subitem 1 in slot 2 of my_list"
my_list[[1]][[3]] <- "subitem 1 in slot 3 of my_list"
my_list[[
my_list
[[1]]
[1] "subitem 1 in slot 1 of my_list" "subitem 1 in slot 2 of my_list"
[3] "subitem 1 in slot 3 of my_list" "4"
[5] "5"
$`slot 2`
[1] "contents of slot named slot 2"
9.2 Making your own objects
We’ve covered one type of object, which is a list. You saw it was quite flexible. How many types of objects are there?
There are an infinite number of objects, because people make their own class of object. You can detect the type of the object (the class) by the function class
Object can be said to be an instance of a class.
Analogies:
class - Pokemon, object - Pikachu
class - Book, object - To Kill a Mockingbird
class - DataFrame, object - 2010 census data
class - Character, object - “Programming is Fun”
What is type (class) of object is cen10
?
class(cen10)
[1] "spec_tbl_df" "tbl_df" "tbl" "data.frame"
What about this text?
class("some random text")
[1] "character"
To change or create the class of any object, you can assign it. To do this, assign the name of your class to character to an object’s class()
.
We can start from a simple list. For example, say we wanted to store data about pokemon. Because there is no pre-made package for this, we decide to make our own class.
<- list(
pikachu name = "Pikachu",
number = 25,
type = "Electric",
color = "Yellow"
)
and we can give it any class name we want.
class(pikachu) <- "Pokemon"
str(pikachu)
List of 4
$ name : chr "Pikachu"
$ number: num 25
$ type : chr "Electric"
$ color : chr "Yellow"
- attr(*, "class")= chr "Pokemon"
$type pikachu
[1] "Electric"
9.2.1 Seeing R through objects
Most of the R objects that you will see as you advance are their own objects. For example, here’s a linear regression object (which you will learn more about in Gov 2000):
<- lm(mpg ~ wt + vs + gear + carb, mtcars)
ols class(ols)
[1] "lm"
Anything can be an object! Even graphs (in ggplot
) can be assigned, re-assigned, and edited.
<- group_by(cen10, race) |>
grp_race summarize(count = n())
<- arrange(grp_race, count) |>
grp_race_ordered mutate(race = forcats::as_factor(race))
<- ggplot(data = grp_race_ordered) +
gg_tab aes(x = race, y = count) +
geom_col() +
labs(caption = "Source: U.S. Census 2010")
gg_tab
You can change the orientation
<- gg_tab + coord_flip() gg_tab
9.2.2 Parsing an object by str()s
It can be hard to understand an R
object because it’s contents are unknown. The function str
, short for structure, is a quick way to look into the innards of an object
str(my_list)
List of 2
$ : chr [1:5] "subitem 1 in slot 1 of my_list" "subitem 1 in slot 2 of my_list" "subitem 1 in slot 3 of my_list" "4" ...
$ slot 2: chr "contents of slot named slot 2"
class(my_list)
[1] "list"
Same for the object we just made
str(pikachu)
List of 4
$ name : chr "Pikachu"
$ number: num 25
$ type : chr "Electric"
$ color : chr "Yellow"
- attr(*, "class")= chr "Pokemon"
What does a ggplot
object look like? Very complicated, but at least you can see it:
# enter this on your console
str(gg_tab)
9.3 Types of variables
In the social science we often analyze variables. As you saw in the tutorial, different types of variables require different care.
A key link with what we just learned is that variables are also types of R objects.
9.3.1 scalars
One number. How many people did we count in our Census sample?
nrow(cen10)
[1] 30871
Question: What proportion of our census sample is Native American? This number is also a scalar
# Enter yourself
unique(cen10$race)
[1] "White" "Black/Negro"
[3] "Other race, nec" "American Indian or Alaska Native"
[5] "Chinese" "Other Asian or Pacific Islander"
[7] "Two major races" "Three or more major races"
[9] "Japanese"
mean(cen10$race == "American Indian or Alaska Native")
[1] 0.009555894
Hint: you can use the function mean()
to calcualte the sample mean. The sample proportion is the mean of a sequence of number, where your event of interest is a 1 (or TRUE
) and others are 0 (or FALSE
).
9.3.2 numeric vectors
A sequence of numbers.
$count grp_race_ordered
[1] 77 88 295 354 869 1129 1839 4013 22207
class(grp_race_ordered$count)
[1] "integer"
Or even, all the ages of the millions of people in our Census. Here are just the first few numbers of the list.
head(cen10$age)
[1] 8 24 37 12 18 50
9.3.3 characters (aka strings)
This can be just one stretch of characters
<- "Meg"
my_name my_name
[1] "Meg"
class(my_name)
[1] "character"
or more characters. Notice here that there’s a difference between a vector of individual characters and a length-one object of characters.
<- c("M", "e", "g")
my_name_letters my_name_letters
[1] "M" "e" "g"
class(my_name_letters)
[1] "character"
Finally, remember that lower vs. upper case matters in R!
<- "shiro"
my_name2 == my_name2 my_name
[1] FALSE
9.4 What is a function?
Most of what we do in R is executing a function. read_csv()
, nrow()
, ggplot()
.. pretty much anything with a parentheses is a function. And even things like <-
and [
are functions as well.
A function is a set of instructions with specified ingredients. It takes an input, then manipulates it – changes it in some way – and then returns the manipulated product.
One way to see what a function actually does is to enter it without parentheses.
# enter this on your console
table
You’ll see below that the most basic functions are quite complicated internally.
You’ll notice that functions contain other functions. wrapper functions are functions that “wrap around” existing functions. This sounds redundant, but it’s an important feature of programming. If you find yourself repeating a command more than two times, you should make your own function, rather than writing the same type of code.
9.4.1 Write your own function
It’s worth remembering the basic structure of a function. You create a new function, call it my_fun
by this:
<- function() {
my_fun
}
If we wanted to generate a function that computed the number of men in your data, what would that look like?
<- function(data) {
count_men <- sum(data$sex == "Male")
nmen
return(nmen)
}
Then all we need to do is feed this function a dataset
count_men(cen10)
[1] 15220
The point of a function is that you can use it again and again without typing up the set of constituent manipulations. So, what if we wanted to figure out the number of men in California?
count_men(cen10[cen10$state == "California", ])
[1] 1876
Let’s go one step further. What if we want to know the proportion of non-whites in a state, just by entering the name of the state? There’s multiple ways to do it, but it could look something like this
<- function(data, state) {
nw_in_state <- data[data$state == state, ]
s.subset <- nrow(s.subset)
total.s <- sum(s.subset$race != "White")
nw.s
/ total.s
nw.s }
The last line is what gets generated from the function. To be more explicit you can wrap the last line around return()
. (as in return(nw.s/total.s
). return()
is used when you want to break out of a function in the middle of it and not wait till the last line.
Try it on your favorite state!
nw_in_state(cen10, "Massachusetts")
[1] 0.2040185
Checkpoint
1
Try making your own function, average_age_in_state
, that will give you the average age of people in a given state.
# Enter on your own
2
Try making your own function, asians_in_state
, that will give you the number of Chinese
, Japanese
, and Other Asian or Pacific Islander
people in a given state.
# Enter on your own
3
Try making your own function, ‘top_10_oldest_cities’, that will give you the names of cities whose population’s average age is top 10 oldest.
# Enter on your own
9.5 What is a package?
You can think of a package as a suite of functions that other people have already built for you to make your life easier.
help(package = "ggplot2")
To use a package, you need to do two things: (1) install it, and then (2) load it.
Installing is a one-time thing
install.packages("ggplot2")
But you need to load each time you start a R instance. So always keep these commands on a script.
library(ggplot2)
In rstudio.cloud
, we already installed a set of packages for you. But when you start your own R instance, you need to have installed the package at some point.
9.6 Conditionals
Sometimes, you want to execute a command only under certain conditions. This is done through the almost universal function, if()
. Inside the if
function we enter a logical statement. The line that is adjacent to, or follows, the if()
statement only gets executed if the statement returns TRUE
.
For example,
For example,
<- 5
x if (x > 0) {
print("positive number")
else if (x == 0) {
} print("zero")
else {
} print("negative number")
}
[1] "positive number"
You can wrap that whole things in a function
<- function(number) {
is_positive if (number > 0) {
print("positive number")
else if (number == 0) {
} print("zero")
else {
} print("negative number")
}
}
is_positive(5)
[1] "positive number"
is_positive(-3)
[1] "negative number"
9.7 For-loops
Loops repeat the same statement, although the statement can be “the same” only in an abstract sense. Use the for(x in X)
syntax to repeat the subsequent command as many times as there are elements in the right-hand object X
. Each of these elements will be referred to the left-hand index x
First, come up with a vector.
<- c("apples", "oranges", "grapes") fruits
Now we use the fruits
vector in a for
loop.
for (fruit in fruits) {
print(paste("I love", fruit))
}
[1] "I love apples"
[1] "I love oranges"
[1] "I love grapes"
Here for()
and in
must be part of any for loop. The right hand side fruits
must be a thing that exists. Finally the left-hand
side object is “Pick your favor name.” It is analogous to how we can index a sum with any letter. \(\sum_{i=1}^{10}i\) and sum_{j = 1}^{10}j
are in fact the same thing.
for (i in 1:length(fruits)) {
print(paste("I love", fruits[i]))
}
[1] "I love apples"
[1] "I love oranges"
[1] "I love grapes"
<- c("California", "Massachusetts", "New Hampshire", "Washington")
states_of_interest
for (state in states_of_interest) {
<- cen10[cen10$state == state, ]
state_data <- sum(state_data$sex == "Male")
nmen
<- nrow(state_data)
n <- round(100 * (nmen / n), digits = 2)
men_perc print(paste("Percentage of men in", state, "is", men_perc))
}
[1] "Percentage of men in California is 49.85"
[1] "Percentage of men in Massachusetts is 47.6"
[1] "Percentage of men in New Hampshire is 48.55"
[1] "Percentage of men in Washington is 48.19"
Instead of printing, you can store the information in a vector
<- c("California", "Massachusetts", "New Hampshire", "Washington")
states_of_interest <- c()
male_percentages <- 1
iter
for (state in states_of_interest) {
<- cen10[cen10$state == state, ]
state_data <- sum(state_data$sex == "Male")
nmen <- nrow(state_data)
n <- round(100 * (nmen / n), digits = 2)
men_perc
<- c(male_percentages, men_perc)
male_percentages names(male_percentages)[iter] <- state
<- iter + 1
iter
}
male_percentages
California Massachusetts New Hampshire Washington
49.85 47.60 48.55 48.19
9.8 Nested Loops
What if I want to calculate the population percentage of a race group for all race groups in states of interest? You could probably use tidyverse functions to do this, but let’s try using loops!
<- c("California", "Massachusetts", "New Hampshire", "Washington")
states_of_interest for (state in states_of_interest) {
for (race in unique(cen10$race)) {
<- nrow(cen10[cen10$race == race & cen10$state == state, ])
race_state_num <- nrow(cen10[cen10$state == state, ])
state_pop <- round(100 * (race_state_num / (state_pop)), digits = 2)
race_perc print(paste("Percentage of ", race, "in", state, "is", race_perc))
} }
[1] "Percentage of White in California is 57.61"
[1] "Percentage of Black/Negro in California is 6.72"
[1] "Percentage of Other race, nec in California is 15.55"
[1] "Percentage of American Indian or Alaska Native in California is 1.12"
[1] "Percentage of Chinese in California is 3.75"
[1] "Percentage of Other Asian or Pacific Islander in California is 9.54"
[1] "Percentage of Two major races in California is 4.62"
[1] "Percentage of Three or more major races in California is 0.37"
[1] "Percentage of Japanese in California is 0.72"
[1] "Percentage of White in Massachusetts is 79.6"
[1] "Percentage of Black/Negro in Massachusetts is 5.87"
[1] "Percentage of Other race, nec in Massachusetts is 4.02"
[1] "Percentage of American Indian or Alaska Native in Massachusetts is 0.77"
[1] "Percentage of Chinese in Massachusetts is 2.32"
[1] "Percentage of Other Asian or Pacific Islander in Massachusetts is 4.33"
[1] "Percentage of Two major races in Massachusetts is 2.78"
[1] "Percentage of Three or more major races in Massachusetts is 0"
[1] "Percentage of Japanese in Massachusetts is 0.31"
[1] "Percentage of White in New Hampshire is 93.48"
[1] "Percentage of Black/Negro in New Hampshire is 0.72"
[1] "Percentage of Other race, nec in New Hampshire is 0.72"
[1] "Percentage of American Indian or Alaska Native in New Hampshire is 0.72"
[1] "Percentage of Chinese in New Hampshire is 0.72"
[1] "Percentage of Other Asian or Pacific Islander in New Hampshire is 2.17"
[1] "Percentage of Two major races in New Hampshire is 0.72"
[1] "Percentage of Three or more major races in New Hampshire is 0"
[1] "Percentage of Japanese in New Hampshire is 0.72"
[1] "Percentage of White in Washington is 76.05"
[1] "Percentage of Black/Negro in Washington is 2.9"
[1] "Percentage of Other race, nec in Washington is 5.37"
[1] "Percentage of American Indian or Alaska Native in Washington is 2.03"
[1] "Percentage of Chinese in Washington is 1.31"
[1] "Percentage of Other Asian or Pacific Islander in Washington is 6.68"
[1] "Percentage of Two major races in Washington is 4.79"
[1] "Percentage of Three or more major races in Washington is 0.29"
[1] "Percentage of Japanese in Washington is 0.58"
Exercises
Exercise 1: Write your own function
Write your own function that makes some task of data analysis simpler. Ideally, it would be a function that helps you do either of the previous tasks in fewer lines of code. You can use the three lines of code that was provided in exercise 1 to wrap that into another function too!
# Enter yourself
Exercise 2: Using Loops
Using a loop, create a crosstab of sex and race for each state in the set “states_of_interest”
<- c("California", "Massachusetts", "New Hampshire", "Washington")
states_of_interest # Enter yourself
Exercise 3: Storing information derived within loops in a global dataframe
Recall the following nested loop
<- c("California", "Massachusetts", "New Hampshire", "Washington")
states_of_interest for (state in states_of_interest) {
for (race in unique(cen10$race)) {
<- nrow(cen10[cen10$race == race & cen10$state == state, ])
race_state_num <- nrow(cen10[cen10$state == state, ])
state_pop <- round(100 * (race_state_num / (state_pop)), digits = 2)
race_perc print(paste("Percentage of ", race, "in", state, "is", race_perc))
} }
[1] "Percentage of White in California is 57.61"
[1] "Percentage of Black/Negro in California is 6.72"
[1] "Percentage of Other race, nec in California is 15.55"
[1] "Percentage of American Indian or Alaska Native in California is 1.12"
[1] "Percentage of Chinese in California is 3.75"
[1] "Percentage of Other Asian or Pacific Islander in California is 9.54"
[1] "Percentage of Two major races in California is 4.62"
[1] "Percentage of Three or more major races in California is 0.37"
[1] "Percentage of Japanese in California is 0.72"
[1] "Percentage of White in Massachusetts is 79.6"
[1] "Percentage of Black/Negro in Massachusetts is 5.87"
[1] "Percentage of Other race, nec in Massachusetts is 4.02"
[1] "Percentage of American Indian or Alaska Native in Massachusetts is 0.77"
[1] "Percentage of Chinese in Massachusetts is 2.32"
[1] "Percentage of Other Asian or Pacific Islander in Massachusetts is 4.33"
[1] "Percentage of Two major races in Massachusetts is 2.78"
[1] "Percentage of Three or more major races in Massachusetts is 0"
[1] "Percentage of Japanese in Massachusetts is 0.31"
[1] "Percentage of White in New Hampshire is 93.48"
[1] "Percentage of Black/Negro in New Hampshire is 0.72"
[1] "Percentage of Other race, nec in New Hampshire is 0.72"
[1] "Percentage of American Indian or Alaska Native in New Hampshire is 0.72"
[1] "Percentage of Chinese in New Hampshire is 0.72"
[1] "Percentage of Other Asian or Pacific Islander in New Hampshire is 2.17"
[1] "Percentage of Two major races in New Hampshire is 0.72"
[1] "Percentage of Three or more major races in New Hampshire is 0"
[1] "Percentage of Japanese in New Hampshire is 0.72"
[1] "Percentage of White in Washington is 76.05"
[1] "Percentage of Black/Negro in Washington is 2.9"
[1] "Percentage of Other race, nec in Washington is 5.37"
[1] "Percentage of American Indian or Alaska Native in Washington is 2.03"
[1] "Percentage of Chinese in Washington is 1.31"
[1] "Percentage of Other Asian or Pacific Islander in Washington is 6.68"
[1] "Percentage of Two major races in Washington is 4.79"
[1] "Percentage of Three or more major races in Washington is 0.29"
[1] "Percentage of Japanese in Washington is 0.58"
Instead of printing the percentage of each race in each state, create a dataframe, and store all that information in that dataframe. (Hint: look at how I stored information about male percentage in each state of interest in a vector.)