library(dplyr) # used in Part 3.3 onlyHW2: Data Import, Cleaning, and Exploration in R
Due Monday, October 12, 2026, 11:59 pm (Pacific)
Overview
📄 Download this assignment as a Quarto file (HW2.qmd): open it in Positron and click Preview to get a standalone HTML copy you can read offline.
In this assignment you will work with the PalmTraits 1.0 dataset, a global database of functional traits for more than 2,500 palm species. It is real data, so it needs cleaning before it can be analyzed. You will import it, clean it with base R, summarize it (including one dplyr summary), and explore it with plots.
Everything is done in Positron, in a single Quarto document. You submit that .qmd file and its rendered HTML.
The required parts are guided: each step tells you which functions to use and what to look for. The Optional Extensions at the end are not graded. They are good practice for HW3 and the midterm, and we can go over them in the Wednesday session.
AI tools are not permitted on this homework (see the syllabus AI Policy): the point is to build the coding fluency you need for the midterm, where no AI is available. No tool is required, and no one is advantaged by paying for one. Where AI would fail here: it may hallucinate PalmTraits column names or coding conventions, write cleaning code that silently drops or recodes rows incorrectly, and give confident but wrong descriptions of the data. Only the dataset documentation and your own checks (str(), summary(), row counts) can tell you what is true.
Homework check: I may ask you to briefly explain one of your solutions in the Wednesday section (random sample, about 5 minutes). Be ready to walk through your own code and reasoning.
Learning Objectives
By completing this assignment, you will be able to:
- Set up a reproducible project in Positron and write your analysis as a Quarto document
- Import a tab-delimited text file into R and inspect it
- Find and handle missing values, fix data types, and create new variables with base R
- Calculate descriptive statistics and frequency tables, and interpret them in context
- Create and interpret histograms, boxplots, and scatterplots
About the Dataset
The PalmTraits 1.0 database contains functional trait data for palm species (family Arecaceae). It captures traits related to:
- Growth forms (climbing, erect, acaulescent = stemless)
- Stem characteristics (height, diameter, armature/spines)
- Leaf characteristics (blade length, number of leaves)
- Fruit characteristics (length, width, color, shape)
For the growth-form and armature variables, 0 = absent, 1 = present, and 2 = both states occur in the species.
Citation: Kissling, W.D., Balslev, H., Baker, W.J., Dransfield, J., Göldel, B., Lim, J.Y., Onstein, R.E., & Svenning, J.-C. (2019). PalmTraits 1.0, a species-level functional trait database of palms worldwide. Scientific Data, 6, 178. https://www.nature.com/articles/s41597-019-0189-0
Part 0: Set Up Your Project in Positron (5 points)
0.1 Create a project folder
- In Positron, choose File → Open Folder… and create/open a new folder called
STAT204-HW2(you can put it inside the Git repository you set up in Week 1). - In the Explorer pane (left sidebar), create a subfolder called
data.
Opening the folder matters: Positron’s console and Quarto both use it as the working directory, so relative paths like data/... just work. Do not use setwd() in your document.
0.2 Download the dataset
- Go to: https://datadryad.org/stash/dataset/doi:10.5061/dryad.ts45225
- Click “Download Dataset” (free and open access) and extract the files.
- Find
PalmTraits_1.0.txt(a tab-delimited text file, under the Sep 09, 2020 tab) and move it into yourdata/folder.
0.3 Create your Quarto document
- Choose File → New File… → Quarto Document, and save it in the project folder (not in
data/) asLastName_FirstName_HW2.qmd. - Check the interpreter picker in the top-right corner of Positron: it should show an R session. If it shows Python, click it and select R.
- Replace the default header with:
---
title: "HW2: Data Import, Cleaning, and Exploration"
author: "Your Name"
date: today
format:
html:
toc: true
---- Add a short paragraph saying where the data comes from (the Dryad link and the file name), then this setup chunk:
Working in Positron:
- Run the current line or selection with Ctrl/Cmd + Enter, or a whole chunk with Run Cell above it.
- The Variables pane shows every object you create. Click the table icon next to a data frame (or run
View(palm_data)in the console) to open it in the Data Explorer. - Preview your document often (the Preview button, or Ctrl/Cmd + Shift + K). If it renders, your code runs from top to bottom in a fresh session, which is what “reproducible” means.
- Optional: commit your work to Git from the Source Control pane as you go, like you practiced in Week 1.
Points: the document renders without errors, uses a relative path for the data, and has a setup chunk.
Part 1: Import and First Look (15 points)
1.1 Import the data (8 points)
# palm_data <- read.delim("data/PalmTraits_1.0.txt",
# header = TRUE,
# stringsAsFactors = FALSE)Instructions:
- Import the file with
read.delim()(it is tab-delimited, which is the default forread.delim()) - Show
dim(palm_data),names(palm_data), andhead(palm_data, 5) - Show
str(palm_data) - In one or two sentences: how many species and variables are there? Which variables did R read as numbers, and which as text?
1.2 Guided exploration in the Data Explorer (7 points)
Open palm_data in Positron’s Data Explorer (Variables pane → table icon). Use the column headers to sort and the filter bar to filter. The summary panel on the left shows the percentage of missing values for each column.
Answer these in your document, in text:
Sort by
MaxStemHeight_mfrom largest to smallest. Which species is the tallest, and what is its growth form (look atClimbing)? Does a “height” of that size make sense for that growth form?Filter to
Climbingequal to 1. How many climbing species are there?Which of these columns has the most missing values:
MaxStemHeight_m,Max_Blade_Length_m,AverageFruitLength_cm,AverageFruitWidth_cm?Look at the
UnderstoreyCanopycolumn. Besidesunderstoreyandcanopy, what other values appear? Is anything missing that R would not count asNA?
Then confirm (a) and (b) with code, so the answers appear in your rendered document:
# palm_data$SpecName[which.max(palm_data$MaxStemHeight_m)]
# sum(palm_data$Climbing == 1)
# table(palm_data$UnderstoreyCanopy)Part 2: Data Cleaning with Base R (35 points)
2.1 Find the missing values (8 points)
# Missing values in the whole dataset, and per column
# sum(is.na(palm_data))
# colSums(is.na(palm_data))
# Percentage missing per column, sorted
# pct_missing <- round(100 * colMeans(is.na(palm_data)), 1)
# sort(pct_missing, decreasing = TRUE)
# Duplicated rows
# sum(duplicated(palm_data))Instructions: run the code above, then write two or three sentences: which kinds of traits (stem, leaf, fruit) have the most missing data, and are there duplicated rows?
2.2 Create a working subset (5 points)
Keep only these columns:
# keep <- c("SpecName", "Climbing", "Acaulescent", "Erect", "StemArmed",
# "MaxStemHeight_m", "Max_Blade_Length_m",
# "AverageFruitLength_cm", "AverageFruitWidth_cm")
# palm_subset <- palm_data[, keep]
# dim(palm_subset)Report how many variables and observations your subset has.
2.3 Handle missing values (8 points)
We will keep the species that have both a stem height and a fruit length, since Parts 3 and 4 use those two variables the most:
# key_vars <- c("MaxStemHeight_m", "AverageFruitLength_cm")
# palm_clean <- palm_subset[complete.cases(palm_subset[, key_vars]), ]
# nrow(palm_subset) - nrow(palm_clean) # how many rows were removed?
# colSums(is.na(palm_clean)) # what is still missing?Instructions: run the code and answer in two or three sentences:
- How many species did you remove, and what percentage of the data is that?
- Which variables still have missing values? (That’s fine: use
na.rm = TRUEwhen you summarize them.) - Could removing these species bias the results? Think about which kinds of palms are more likely to be missing measurements.
2.4 Fix data types (6 points)
StemArmed is stored as a number, but it is really a category. Convert it to a factor with readable labels, and check the result:
# str(palm_clean$StemArmed)
# palm_clean$StemArmed <- factor(palm_clean$StemArmed,
# levels = c(0, 1),
# labels = c("Unarmed", "Armed"))
# table(palm_clean$StemArmed, useNA = "ifany")Instructions: do the same for Climbing, using levels = c(0, 1, 2) and labels = c("No", "Yes", "Both"). Show str(palm_clean) after your conversions, and explain in one or two sentences why it matters that these variables are factors rather than numbers (think about what mean() or summary() would give you).
2.5 Create new variables (8 points)
- Size category from stem height. Run this code and show the table:
# palm_clean$size_category <- ifelse(palm_clean$MaxStemHeight_m <= 5, "Small",
# ifelse(palm_clean$MaxStemHeight_m <= 15, "Medium",
# ifelse(palm_clean$MaxStemHeight_m <= 30, "Tall", "Very Tall")))
# palm_clean$size_category <- factor(palm_clean$size_category,
# levels = c("Small", "Medium", "Tall", "Very Tall"))
# table(palm_clean$size_category)- Fruit aspect ratio: create
fruit_ratio=AverageFruitLength_cm/AverageFruitWidth_cm. Usesummary()on it. What does a ratio above 1 mean about the shape of the fruit? How many values areNA, and why?
Part 3: Descriptive Statistics (20 points)
3.1 Summary statistics (10 points)
For MaxStemHeight_m and AverageFruitLength_cm, calculate the mean, median, standard deviation, and the quartiles:
# x <- palm_clean$MaxStemHeight_m
# mean(x); median(x); sd(x)
# quantile(x, probs = c(0, 0.25, 0.5, 0.75, 1))Organize the results in a small data frame with one row per variable and one column per statistic, and print it.
Interpret: For each variable, compare the mean and the median. What does the difference tell you about the shape of the distribution?
3.2 Frequency tables (5 points)
# table(palm_clean$size_category)
# prop.table(table(palm_clean$size_category))
# Two-way table: size category vs armature, with row proportions
# tab <- table(palm_clean$size_category, palm_clean$StemArmed)
# tab
# round(prop.table(tab, margin = 1), 2)Interpret: Which size category has the highest proportion of armed (spiny) palms? Write one sentence.
3.3 Group summaries with dplyr (5 points)
group_by() splits the data into groups, and summarize() calculates statistics for each group. The pipe %>% (read it as “then”) passes the result of one step to the next:
# palm_clean %>% # start with the clean data, THEN
# group_by(StemArmed) %>% # group it by armature, THEN
# summarize(
# n = n(), # species per group
# mean_height = mean(MaxStemHeight_m),
# sd_height = sd(MaxStemHeight_m),
# median_fruit = median(AverageFruitLength_cm)
# )Instructions: run this summary, then change it to group by size_category and add one more statistic of your choice (for example max() of stem height, or the median of Max_Blade_Length_m with na.rm = TRUE). Store the result as palm_summary and print it. (If you see an NA group, those are species whose armature is unknown.) Write one or two sentences about what it shows.
Part 4: Visualizations with Base R (15 points)
For each plot, add a title and axis labels with units, and write one or two sentences interpreting it.
4.1 Histogram of stem height (5 points)
Use hist() on MaxStemHeight_m. Describe the shape. Then make a second histogram of log10(MaxStemHeight_m + 1) and compare. (Why + 1? Look for zeros in the data.)
4.2 Boxplot of stem height by armature (5 points)
Use boxplot(MaxStemHeight_m ~ StemArmed, data = palm_clean, ...). Are armed palms taller or shorter than unarmed palms, typically? Are there many outliers?
4.3 Scatterplot of fruit dimensions (5 points)
Plot AverageFruitWidth_cm (y) against AverageFruitLength_cm (x). Add the line abline(0, 1), where length equals width. What does it mean for a species to be above or below that line? Use cor(..., use = "complete.obs") to report the correlation.
Part 5: Reflection (10 points)
Write about 150 words addressing:
- Cleaning decisions: What was the biggest data quality issue you found, and how did your choices in Part 2 change the data you analyzed?
- Findings: What are the two most interesting patterns you found about palm size, armature, or fruit shape?
- Workflow: What was one thing that Positron’s Data Explorer or rendering your Quarto document helped you catch?
Optional Extensions
These are not graded. They are good practice for HW3 and the midterm. If you do them, put them in a section called “Optional” at the end of your document.
- Outliers. For
MaxStemHeight_m, compute Q1, Q3, and the IQR withquantile(), and flag values above Q3 + 1.5 × IQR. List the flagged species. Are they errors, or real extreme values? (Some palms, like Ceroxylon quindiuense, exceed 60 m, and climbing Calamus can exceed 100 m in length.) Decide whether to keep, remove, or flag them, and explain why. - A primary growth form variable. Combine
Climbing,Acaulescent, andErectinto a single factor with levels “Climbing”, “Acaulescent”, “Erect”, and “Mixed”, using nestedifelse(). Make a frequency table, then redo the boxplot in 4.2 grouped by growth form. - More pipes. Use
%>%to (a) keep only climbing palms, (b) select three columns and show the first 5 rows, and (c) compute the mean stem height. - Grouping by two variables. Repeat 3.3 with
group_by(size_category, StemArmed). - Base R vs. dplyr. Reproduce your
palm_summarymeans withtapply()oraggregate(). Which approach do you find easier to read? - Multi-panel figure. Use
par(mfrow = c(2, 2))to combine your plots and abarplot()of size-category frequencies into one figure.
Submission Instructions
Submit to Canvas:
- Your
LastName_FirstName_HW2.qmdfile - The rendered
LastName_FirstName_HW2.html(withembed-resources: true, it is a single file that includes your plots)
You do not need to submit the data file. Your document must say where to download it and use the exact path data/PalmTraits_1.0.txt.
Reproducibility checklist
Before you submit:
- ✅ The data is read with a relative path:
read.delim("data/PalmTraits_1.0.txt"), not"C:/Users/...", and there is nosetwd(). - ✅ All packages are loaded in the setup chunk (only
dplyris needed). - ✅ Restart R (the restart button at the top of the Console pane), then Preview again. If it renders from a fresh session, it will render on my computer too.
- ✅ Every chunk has a descriptive label (
#| label: import-data), and your code has comments. - ✅ Your written answers are in the text of the document, not only in code comments.
Grading
| Part | Points |
|---|---|
| Part 0: Project setup and rendering | 5 |
| Part 1: Import and first look | 15 |
| Part 2: Data cleaning | 35 |
| Part 3: Descriptive statistics | 20 |
| Part 4: Visualizations | 15 |
| Part 5: Reflection | 10 |
| Total | 100 |
Within each part, points are for code that runs and is correct, clear interpretations in context, and well-labeled plots. Optional extensions are not graded.
Due Date
Monday, October 12, 2026, 11:59 pm (Pacific).
Helpful Resources
About the PalmTraits Dataset
- Original paper: Kissling et al. (2019) Scientific Data, DOI: 10.1038/s41597-019-0189-0
- Dataset location: https://datadryad.org/stash/dataset/doi:10.5061/dryad.ts45225
- Documentation: The Dryad repository includes detailed metadata about all variables
Positron and Quarto
- Positron documentation: https://positron.posit.co/
- Positron Data Explorer: https://positron.posit.co/data-explorer.html
- Quarto with R: https://quarto.org/docs/computations/r.html
R Documentation
- Data import:
?read.delim - Missing values:
?is.na,?complete.cases - Statistics:
?mean,?median,?sd,?quantile,?table,?prop.table - Plotting:
?hist,?boxplot,?plot,?abline - dplyr:
?dplyr::group_by,?dplyr::summarize
Getting Help
- Type
?function_namein the Positron console - Break complex operations into smaller steps, and check each one in the Variables pane
- Check that your variable names match exactly (R is case-sensitive)
- Make sure you’re using the right data frame after each cleaning step (
palm_data→palm_subset→palm_clean)
