Lesson 1: Intro to R and Handling Data

EOH 635 — Environmental Epidemiology Methods

Author

Leah Schinasi

Published

October 14, 2026

======================================================

📌 Introduction to R and Data Handling in RStudio

======================================================

There is lots of syntax to learn for working in R. A lot of this will come in time, with practice, and trial and error. Here are some basics to get you started.

Remember learning R is like learning a language. It will get easier, in time.

ImportantHow to use this tutorial

The gray boxes below are live R editors — the code actually runs in your browser (no install needed). In the top-left corner of each gray box, you’ll see a green arrow button labeled “Run Code.” Click that green arrow to run the code in that box. The output (whatever the code produces — a number, a table, a plot, etc.) will appear directly underneath the box a moment later.

You can also edit the code first — click inside the box, change something, then click the green Run Code arrow to run your edited version. Nothing you do here can break anything; if you make a mess, just refresh the page to start that box over.

Some sections are marked 🧠 Your Turn. Those give you a task and a blank (or partially blank) code editor. Try to write the code yourself before you look at the answer — the solution is hidden underneath in a collapsed box. Click it open only after you’ve given it a real attempt (or if you’re truly stuck).

A note on browser vs. RStudio code: Two chunks in this lesson run automatically in the background (you’ll see them labeled #| autorun: true). These chunks fetch the data files from the web so they’re available in your browser — you don’t need to do anything with them. When you write your own scripts in RStudio, you will not include this fetching code. Immediately after each autorun chunk, you’ll find a green box labeled “In your RStudio script” showing the simpler code you’ll actually use.

NoteDoing this in RStudio instead of (or in addition to) the browser?

This lab uses three files, also described in the Data Codebook:

  • class_data_raw.csv — the primary participant-level data set
  • codebook.xlsx — variable descriptions and coding
  • PHL_NDVI_summer_annual.csv — tract-level greenness data you’ll link in

Download all three into the data_raw/ folder of the EOH635_project you built in the C01 setup assignment before you start.


🛠 1. Basic R Concepts

There are lots of amazing resources on using R on the web! Don’t be shy about exploring these.

The following text is excerpted from this resource:

“Let’s start off by introducing some features of the Console. The Console is a tab in RStudio where we can run R code.

Notice that the window pane where the console is located contains three tabs: Console, Terminal and Jobs (this may vary depending on the version of RStudio in use). We’ll focus on the Console for now.

When we open RStudio, the console contains information about the version of R we’re working with.”

Below, instead of typing into RStudio’s console, you can just type directly into the editors on this page and click Run Code.

Assignment Operator (<-)

Creating a Simple Data Frame

We can read a data frame into R, but we can also create a data frame within R itself.

The following code creates a data frame called my_data:

The data frame has two variables (columns): name and age. It has two rows, each corresponding to one person.

Accessing Columns in a Data Frame ($)

We may want to view a particular column in the data frame, or, later when we start conducting analyses, refer to a particular variable. We do this using the $ sign.

Indexing Rows and Columns [row, column]

We can also use R’s indexing system to refer to a particular row or column.

Tip🧠 Your Turn

Using my_data, write one line of code that returns Bob’s age using $-and-indexing together (i.e., pull the age column, then index the second element).

How this works, piece by piece:

  • my_data$age pulls out the entire age column from my_data — this returns a vector of all the ages, in row order: 25 30 (Alice’s age, then Bob’s age).
  • [2] then indexes into that vector and grabs its 2nd element — since Bob is in the 2nd row of my_data, this returns his age, 30.
  • Put together, my_data$age[2] reads as “give me the age column, then give me the 2nd value in it.”

This combination — $ to grab a column, then [ ] to grab a specific position within it — comes up constantly in R, so it’s worth being comfortable with both steps individually before chaining them together.

======================================================

📁 2. A Note on Project Organization

======================================================

In RStudio, we need a reliable way to tell R where to find (and save) files. You’ll sometimes see older code use setwd("C:/Users/yourname/YourFolder") to do this — but this course does not use setwd() as the standard workflow. A path like that is tied to one specific computer and breaks the moment the project moves to a different machine (including, for grading, yours to mine).

Instead, this course uses an RStudio Project with a standard folder structure, set up once in the C01 reproducible-workflow assignment:

EOH635_project/
├── EOH635_project.Rproj
├── data_raw/          <- original files exactly as you received them (never edit these by hand)
├── data_processed/    <- new files your scripts create (cleaned, merged, recoded data)
├── scripts/           <- your .R / .qmd / .Rmd files
└── outputs/           <- tables, figures, and other generated results

When you open EOH635_project.Rproj, RStudio automatically sets your working directory to the project root — no setwd() needed. From there, you use relative paths, typically with file.path(), so the same script works on any computer:

read.csv(file.path("data_raw", "class_data_raw.csv"))

For this lab, that means: Please put class_data_raw.csv, PHL_NDVI_summer_annual.csv, and codebook.xlsx in data_raw/, and save anything you create (like the merged dataset at the end of this lab) into data_processed/.

In this browser-based tutorial, there’s no local file system to point to at all — the data files below are fetched automatically for you from the web the moment you load this page, using the same data_raw / data_processed folder names so the code you see here matches what you’ll type in RStudio.

This is important to keep straight: the “fetch from the web” step you’ll see below is a workaround that exists only on this tutorial page, because your browser has no access to any files at all — not the tutorial’s, and not yours. For your actual project, the data will live as real files on your own computer, saved into the data_raw/ folder of the EOH635_project you set up in RStudio. Your project scripts will simply read those files directly off your hard drive with read.csv(file.path("data_raw", "...")) — no downloading, no internet fetch, no data_base_url. Every time this tutorial shows a “fetch from the web” step, look for the green “In your RStudio script” box right after it — that box shows the shorter, simpler line you’ll actually use for your project.

======================================================

📦 3. Loading Required Packages

======================================================

This is a great tutorial on packages in R.

Briefly (quoting from that guide):

“Base R includes pre-installed packages that allow for a fully functioning statistical environment in which a variety of analyses can be conducted. Thousands of user-contributed extension packages are available that provide enhanced functionality with R.

R packages are collections of functions, data, and compiled code in a well-defined format.

In order to install an extension package, we can invoke the install.packages() function, which typically downloads the package from CRAN and installs it for use.”

You only need to install a package one time on your computer (upon first use, or after installing/re-installing/updating R). After installing it, you need to load it every session with library().

On your own computer you’d run:

install.packages("tidyverse")
library(tidyverse)
Note

This tutorial page has already installed and loaded tidyverse for you in the background (that’s what the webr: packages: setting in this page does). You can still call library(tidyverse) yourself below — it won’t hurt anything, it’s just already done.

Tip💻 In your RStudio script

This automatic install/load is unique to this tutorial page — it will not happen on your own computer. tidyverse is actually a bundle of several packages (including dplyr and tidyr, which you’ll use a lot), but the browser installing it silently for you is just a convenience built into this page.

When you write your own project scripts in RStudio, you are responsible for installing and loading every package your code uses. For any package (tidyverse or otherwise):

install.packages("tidyverse")  # run this once per computer, the first time you use the package
library(tidyverse)              # run this at the top of every script/session that uses it

If you forget the library() line, R will give you an error like could not find function "mutate" (or whatever function you tried to use) — that’s your signal to go back and load the package first.


======================================================

📊 4. Reading Data into R

======================================================

Recall that we can either create a data frame in R, or read one in. A lot of times, as environmental epidemiologists, we have an already-existing data set that we need to read into R — you’ll primarily do that this quarter.

For this lab, the raw data file is class_data_raw.csv (find it described in the Data Codebook, along with codebook.xlsx). In RStudio, once it’s in data_raw/ inside your EOH635_project, you’d run:

data <- read.csv(file.path("data_raw", "class_data_raw.csv"))

In the browser, we first have to fetch the file from the web before R can see it (webR can’t see your OneDrive or hard drive — only files that have been downloaded into its in-browser file system). The chunk below does that automatically when the page loads: it creates a data_raw folder right here in your browser’s R session (mirroring the real project structure) and downloads the file into it, then reads it in exactly the way you would in RStudio.

Tip💻 In your RStudio script

The chunk above runs automatically in the browser to fetch the data file from the web. In your own RStudio script, you don’t need any of that. With class_data_raw.csv already saved in your data_raw/ folder, all you need is:

data <- read.csv(file.path("data_raw", "class_data_raw.csv"))

This is the line to copy into your own analysis script.


======================================================

👀 5. Exploring Your Data

======================================================

Now let’s start to have a look at our data frame.

View first few rows:

Things to think about:

  • What do you notice about the structure of the dataset just by looking at these rows?
  • Are there any missing or unusual values that stand out?
  • Do the variable names seem intuitive? Would renaming any of them improve clarity?
  • Does the dataset contain categorical or numerical variables? How can you tell?
  • Are there any obvious errors or inconsistencies in the first few rows?

Let’s view the structure of the dataset:

Some questions:

  • What data types are present in the dataset? Are they what you expected?
  • Are any variables stored in an inappropriate data type (e.g., categorical variables stored as numeric)?
  • How might the structure of the dataset affect downstream analyses?
  • Do any variables have an unexpectedly small or large number of unique values?
  • Are there any factors with many levels? Should any be combined or reclassified?

Most of the individual-level variables (gender, education, race_ethnicity, employment_status) come in as character strings rather than factors, and many of the health/behavior indicators (stress, current_smoker, obese, high_blood_pressure, etc.) are stored as numeric 0/1 flags even though they’re conceptually categorical (binary). That’s common in survey data, and it’s exactly the kind of thing str() is good for catching before you run an analysis that assumes the wrong type.

One more thing worth noticing in str(data): geoid10 — the census tract identifier for each participant — shows up as num (numeric), even though it functions as a label, not a quantity. It would never make sense to compute an “average geoid10,” the way it might for age. Because it happens to look like a number, R read it in as one — but ID variables like this are safer to store as character, so nothing downstream ever accidentally tries to do math with them. Let’s fix that now:

Hang onto this — it’ll matter again later in this lesson, when we merge in a second dataset.

We can also view the dimensions of the data frame (rows × columns):

  • Rows show us the number of observations (in this case, people)
  • Columns show us the number of variables

How many rows and columns are in our data frame? Based on this, how many people are in our data frame?

Take a moment to actually answer this from the output above before revealing it below.

dim(data) returns 2000 38: 2,000 rows and 38 columns. Since each row is one participant, that means our data frame contains 2,000 people, each described by 38 variables (columns).


Summary of numerical variables

  • What summary statistics does this function produce?
  • What do you observe about key variables?
  • Are there particular variables for which these summary statistics do not make sense? (Hint: think about what summary() does with a 0/1 variable versus a truly continuous one like age.)

Think through these for a couple of variables yourself before checking below.

age (a genuinely continuous variable): summary() gives six numbers — Min. 18.02, 1st Qu. 29.99, Median 40.36, Mean 44.99, 3rd Qu. 56.24, Max. 116.00 — plus a count of missing values (NA's 3). These are all meaningful here: the middle 50% of participants (between the 1st and 3rd quartiles) are roughly 30 to 56 years old. But that Max. of 116 should catch your eye immediately — a plausible age tops out well below that. That’s exactly the kind of thing summary() is good for catching early, before it quietly distorts an analysis later. (We’ll deal with this specific outlier in Lesson 3.)

high_blood_pressure (a binary 0/1 flag, not a continuous quantity): summary() reports Min. 0, Median 0, Mean 0.3048, Max. 1. The Min./Median/Max. here aren’t wrong, exactly, but they’re not very useful — a variable that can only be 0 or 1 doesn’t have a meaningful “middle” or “spread” the way age does. The one number worth keeping from this output is the Mean, 0.3048 — for a 0/1 variable, the mean is just the proportion coded 1, so about 30.5% of participants have high blood pressure. For everything else about a binary variable’s distribution, a frequency count (table(), coming up in Section 6) is the right tool, not summary().


======================================================

🗃 6. Accessing Specific Variables in a Data Frame

======================================================

Let’s practice looking at particular variables in our data frame.

Access the education column:

OK, we accessed it, but that looks pretty messy and it’s hard to glean much information! We’re just seeing the raw value of education for every single participant, one after another — technically correct, but far too much to scan by eye and make sense of.

Instead, it can be helpful to create summary counts.

  • education is a categorical variable, so we can count the number of participants that fall into each level (category).
  • We use the table() function to do this.
  • We can also look at the proportion and percentage of participants in each category.

Putting this together: the first line, table(), gives raw counts — 1,066 participants coded "< Bachelor's" and 934 coded "Bachelor's degree or higher". prop.table() takes that same table and converts it to proportions of the total (each cell divided by 2,000), and multiplying by 100 turns those proportions into percentages: about 53.3% of participants have less than a bachelor’s degree, and about 46.7% have a bachelor’s degree or higher. All three lines describe the exact same underlying counts — they’re just three different units (counts, proportions, percentages) for reporting the same distribution, and which one you’d report depends on your audience (a raw count is often more useful in a table you’re building for yourself; a percentage usually reads more naturally in written text).

Tip🧠 Your Turn

See if you can do the same thing (raw counts, proportions, and percentages) with the race_ethnicity variable. Then answer these three questions using your output:

  1. How many people identify as Hispanic or Latino (of any race)?
  2. What proportion of the study population identifies as White alone, not Hispanic or Latino?
  3. What percentage of the study population identifies as Asian alone, not Hispanic or Latino?

Type your three answers as comments in the code box below before checking the solution.

Answers:

  1. 275 people identify as Hispanic or Latino (of any race).
  2. 0.61 (about 61%) of the study population identifies as White alone, not Hispanic or Latino.
  3. 4.7% of the study population identifies as Asian alone, not Hispanic or Latino.

======================================================

🔄 7. Creating a New Variable

======================================================

It’s important to understand how to manipulate the variables in your data frame. Consider, again, the education variable.

  • What type of variable is education? Let’s use str() to check how it’s coded in our data frame.

Based on this, we see that education is a character variable — we know that because str() labels it chr right at the start of the output.

str() uses a short abbreviation for every variable type, and it’s worth being able to recognize the common ones at a glance:

Abbreviation Type Example
chr character (text/string) education, gender
int integer (whole numbers) id, year
num numeric (double — can hold decimals) age, hrlctree
logi logical (TRUE/FALSE) rare in survey data, but common after a comparison like age > 65
Factor factor (categorical, with defined levels) not currently used in this dataset — R reads categories in as chr by default, but some analyses expect Factor instead

Creating a numeric version of the education variable

Right now, education is a character variable — useful for reading and for table(), but not for most quantitative work. Regression models, correlations, and most statistical functions in R need numeric input — they can’t compute something like a mean, a correlation coefficient, or a regression slope on the text strings "< Bachelor's" and "Bachelor's degree or higher". To use education status as a predictor or outcome in that kind of analysis later in the course, we first need a consistently coded numeric (here, binary 0/1) version. Let’s do this now, using base R’s ifelse():

Note

A note for later in the course: if you only ever plan to use education as an explanatory variable (a predictor) in a regression model — not as the outcome — converting it to a numeric 0/1 variable isn’t your only option. R’s regression functions (like lm() and glm()) can also take a factor variable directly as a predictor, and will automatically create the 0/1 coding behind the scenes (this is called “dummy coding”). For a variable with just two categories like this one, numeric 0/1 and factor give you the same result either way. The numeric approach shown here is worth learning first because it makes that dummy-coding step explicit rather than hidden — but once you’re comfortable with what’s happening, factor() is often the more natural choice for a categorical predictor, especially one with more than two levels.

What happened here? This line creates a new variable called num_education, a numeric version of education, using ifelse():

  • The code checks each value of education.
  • If the value is "< Bachelor's" (less than a bachelor’s degree), num_education is set to 0.
  • For all other values (presumably at least a bachelor’s degree), num_education is set to 1.

So this line recodes a categorical education variable into a binary (0/1) numeric variable, where:

  • 0 = less than a bachelor’s degree
  • 1 = bachelor’s degree or higher

Note: in R there are lots of ways to do the same thing. Another way to recode this variable is with the dplyr package (loaded as part of tidyverse) instead of base R’s ifelse().

Quick reminder of the syntax before you try it: mutate() adds (or changes) a column in a data frame, and if_else() works like ifelse() — a condition, then the value to use when it’s TRUE, then the value to use when it’s FALSE. Put together, the general pattern looks like:

data <- data %>%
  mutate(new_column_name = if_else(some_condition, value_if_true, value_if_false))
Tip🧠 Your Turn

dplyr’s equivalent of ifelse() is if_else(), typically used inside mutate(). Try rewriting the num_education recode above using data %>% mutate(...) and if_else() instead of base R. Call the new column num_education2 so you can compare it to the original.

Verify that the coding of the new variable worked as we wanted, by creating a two-by-two table looking at the distribution of the numeric education variable against the character one:


======================================================

📝 8. Merging Data with External Information

======================================================

A lot of times in environmental epidemiology, we have additional environmental information that we want to link into our data. Examples include:

  • Air pollution data
  • Housing data
  • Tract-level sociodemographic composition variables
  • Green space data
  • Crime data

You can probably think of additional types of data we might want to link in.

Why would we want to do this? Here’s a concrete example: if we’re interested in studying the association between residential air pollution exposure and asthma diagnosis, our survey data alone won’t have an air pollution measurement for each participant — so we’d want to link in a separate data source that does have it, matched to where each participant lives.

We can do this by merging a second data frame — which has the environmental information of interest — into our primary data frame, by combining them using a common variable or variables.

  • Often, we use information about participants’ neighborhoods (e.g., census tracts) to merge in environmental data. A census tract is a small, relatively permanent geographic area defined by the U.S. Census Bureau — roughly neighborhood-sized, typically a few thousand residents — that’s commonly used in public health research as a proxy for someone’s local environment, since exact addresses usually aren’t available (or shared) for privacy reasons.

Let’s do that now.


Load another dataset

You’ll find an additional data frame, which contains neighborhood-level (tract-level) data on NDVI (greenness), called PHL_NDVI_summer_annual.csv. (For the other tract-level environmental and sociodemographic variables already in data — like hrlctree or pov_allage — see the Data Codebook.)

This data frame shows the median value of the summertime NDVI value for each census tract in Philadelphia — that is, how green each census tract is. NDVI stands for Normalized Difference Vegetation Index. NDVI ranges in value from -1 to 1.

  • Negative values represent water.
  • Values close to 1 mean dense vegetation.
  • Values close to 0 mean a lack of vegetation (there might instead be lots of concrete).

This page tells us a bit more about NDVI.

Let’s read in that data (again, in the browser we first fetch it from the web; in RStudio you’d just need it in your working directory):

Tip💻 In your RStudio script

The chunk above fetches the NDVI file from the web for the browser. In your own RStudio script, with PHL_NDVI_summer_annual.csv already in your data_raw/ folder, all you need is:

PHL_NDVI_summer_annual <- read.csv(file.path("data_raw", "PHL_NDVI_summer_annual.csv"))

Inspect the dataset using the names, str, and head functions. Do you remember what each of these shows you?

Questions:

  • How many observations (rows) are in the NDVI data frame?
  • What are the names of the variables (columns) in this data frame?
  • What seems to be the unique identifier in the NDVI data frame?

Summarize the NDVI values across Philadelphia

Using our Philadelphia tract-level data frame, let’s look at the distribution of NDVI across the city of Philadelphia.

  • What is the minimum NDVI value? What does this tell you?
  • What’s the median? What’s the max?
  • But we want to know how green the neighborhoods of our study participants are.
  • To do so, we’re going to link the NDVI data frame to our primary data frame using a common variable found in both.

The common variable is called geoid10. In our class data frame, geoid10 refers to the census tract in which the study participant lives. In the NDVI data frame, GEOID10 refers to each unique Philadelphia census tract.

In order for the merge/linkage to work, both variables have to be in the same format (both character, or both numeric, etc.). Let’s check on this now.

Notice these don’t match: data$geoid10 shows up as chr, because we deliberately converted it back in Section 5 (it’s a label, not a quantity, remember?) — but PHL_NDVI_summer_annual$GEOID10 is freshly read in from its own file and is still num. This is exactly the situation the earlier conversion was setting up: without it, both would have looked numeric and matched by coincidence, hiding the fact that ID columns need this treatment at all. Since they don’t match now, let’s convert GEOID10 to character too:

(Re-running data$geoid10 <- as.character(data$geoid10) here as well wouldn’t hurt anything — converting an already-character column to character is a harmless no-op — but it’s only necessary for the one that’s still numeric.)

Now let’s merge the datasets, using a left join:

Now the data frame called data2 should have all of our original variables, plus the variables from the NDVI data frame.

As a check, think about how many variables were in each separate data frame.

  • How many rows are in data2?

We’re specifically checking this because a left join (all.x = TRUE) is supposed to keep every row from data — one row per participant, no more and no fewer — while just adding columns from PHL_NDVI_summer_annual onto each one. If the row count in data2 doesn’t match the row count in data (2,000), that’s a red flag: it would mean the merge either dropped some participants, or duplicated some (which happens if the NDVI file accidentally has more than one row per geoid10 — each extra match creates an extra output row). Checking nrow() before and after any merge is one of the simplest, most important habits for confirming a merge did what you expected.

Or we can use dim(), which shows us rows and columns:

We can also look at the names of the variables in the merged data frame — is everything in there?

It’s also helpful to look at the top rows of the merged data frame, to be sure we don’t have a bunch of missing values (coded NA) — if we see missings in the NDVI variables, that likely means something went wrong with our merge!

We can also do some logic checks to be sure everything checks out.

Tip🧠 Your Turn

Do participants living in the identical census tract get assigned the same NDVI value? Write code to check this — for example, group data2 by geoid10 and count the number of distinct X2015_NDVI values within each tract. If the merge worked correctly, every tract should have exactly 1 distinct NDVI value.

Why should that be true? X2015_NDVI is a tract-level measurement — it describes the neighborhood, not the individual person. A left join doesn’t create new NDVI values; it just looks up the one value that belongs to each participant’s geoid10 and copies it onto every row that matches. So everyone sharing a tract should end up with the identical copied value. If any tract shows more than one distinct value, something’s wrong — most likely a formatting issue (like the one we just fixed) that caused some participants to match the wrong row, or a duplicate tract entry in the NDVI file itself.

(Hint: dplyr’s group_by(), summarize(), and n_distinct() are useful here.)

What this output is telling you: the summarize() step produces one row per tract, giving the number of distinct NDVI values found within it (n_unique_ndvi) — then count(n_unique_ndvi) collapses that down into a small frequency table, showing how many tracts (n) had each possible value of n_unique_ndvi. What you’re looking for is a single row: n_unique_ndvi = 1, with n equal to the total number of tracts (384) — meaning every tract, without exception, has exactly one NDVI value. If you instead saw a second row (e.g., n_unique_ndvi = 2 with some nonzero n), that would tell you exactly how many tracts have the problem, which is your starting point for troubleshooting.

Assuming everything looks OK, let’s move on. If not, we’d need to do some troubleshooting!


======================================================

📂 9. Saving Your Processed Data

======================================================

Currently, our new data frame just lives in our R environment (or, on this page, in your browser’s temporary in-browser R session). Once we close R (or this browser tab), we lose our changes unless we save them. We can either rerun this code again, or save our new data frame with any changes we made. Let’s do this now.

Note: it’s good practice to save your data frame using a new name, and — per this course’s project convention — into data_processed/, not data_raw/. That way data_raw/class_data_raw.csv stays untouched as your original source, and anyone (including you, later) can tell at a glance that data_processed/ files were generated by a script rather than hand-edited.

Why does that separation matter so much? Think about reproducibility (recall this from Week 1): the whole point of the data_raw/ → script → data_processed/ pattern is that your script, not a person clicking around in a spreadsheet, is the record of every transformation applied to the data. If class_data_raw.csv ever got hand-edited directly, there would be no way to tell what changed or why — the original data would effectively be lost, and nobody (including future you) could regenerate it from scratch. Keeping data_raw/ untouched means you can always delete data_processed/ entirely and rebuild it by just re-running your script — which is exactly what “reproducible” means in practice.

Tip💻 In your RStudio script

This is the line to use in your own script to save the processed dataset. The data_processed/ folder already exists in your EOH635_project from the setup assignment:

write.csv(data2, file.path("data_processed", "class_data_processed.csv"), row.names = FALSE)

This saves class_data_processed.csv into data_processed/ inside your project, where it will persist between sessions.

You can also run the chunk below in the browser to see how it works — but note that in the browser the file is only saved temporarily and disappears when you close the tab. To actually save this file permanently to your hard drive, run the single line shown in the green “In your RStudio script” box just above, inside RStudio — with data2 in your R environment and data_processed/ already existing in your EOH635_project, that write.csv(...) line writes the file to your computer’s disk, where it will still be there the next time you open RStudio.

======================================================

🚀 10. Quick-Start Script for Your Own Project

======================================================

This script covers only the data preparation work we did in this lesson — reading in the NDVI data, merging it with our primary dataset, and recoding the education variable. It’s not a full analysis; think of it as getting your dataset ready for one. Everything above walked through each of those steps one at a time, with the browser-only fetching code mixed in. Below is everything put together as a single clean script — this is what actually goes in your .R or .qmd file inside EOH635_project/scripts/, running on your own computer with class_data_raw.csv and PHL_NDVI_summer_annual.csv already sitting in data_raw/.

Tip💻 In your RStudio script

Copy the whole block below into a new script in RStudio to get started on your own computer. There’s no downloading step here — R just reads the files straight from your data_raw/ folder.

# ---- Setup ----
library(tidyverse)

# ---- Read in your data ----
data <- read.csv(file.path("data_raw", "class_data_raw.csv"))
PHL_NDVI_summer_annual <- read.csv(file.path("data_raw", "PHL_NDVI_summer_annual.csv"))

# ---- Take a first look ----
head(data)
str(data)
dim(data)
summary(data)

# ---- Recode education into a numeric 0/1 variable ----
data <- data %>%
  mutate(num_education = if_else(education == "< Bachelor's", 0, 1))

# ---- Match variable types before merging ----
data$geoid10 <- as.character(data$geoid10)
PHL_NDVI_summer_annual$GEOID10 <- as.character(PHL_NDVI_summer_annual$GEOID10)

# ---- Merge in the NDVI (greenness) data ----
data2 <- merge(data, PHL_NDVI_summer_annual,
                by.x = "geoid10", by.y = "GEOID10", all.x = TRUE)

# ---- Check that the merge worked ----
dim(data2)
head(data2)

# ---- Save your processed dataset ----
write.csv(data2, file.path("data_processed", "class_data_processed.csv"), row.names = FALSE)

======================================================

🎉 You’re Ready for More!

======================================================

Next steps:

  • Try modifying variables
  • Create new derived columns
  • Perform statistical analyses