View R Code
library(tidyverse)
library(glue)
library(httr2)
library(gt)
library(scales)
library(ggrepel)
library(ggbump)
library(ggforce)The American Time Use Survey (ATUS), a joint initiative of the US Bureau of Labor Statistics and the US Census Bureau, provides a look into how Americans allocate their time. In this report, we analyze over two decades of ATUS microdata (2003–2024) looking for patterns regarding how different demographic groups spend their days. We then apply these findings to design time-use profiles for a new time-tracking app targeting specific user demographics.
This report was first published on April 3, 2026. It was republished on October 6, 2026 after an audit of the analysis code found errors that changed several results. Every table, chart, and number below was regenerated. The main corrections:
library(tidyverse)
library(glue)
library(httr2)
library(gt)
library(scales)
library(ggrepel)
library(ggbump)
library(ggforce)load_atus_data <- function(file_base = c("resp", "rost", "act", "cps")){
if(!dir.exists(file.path("data", "mp02"))){
dir.create(file.path("data", "mp02"), showWarnings=FALSE, recursive=TRUE)
}
file_base <- match.arg(file_base)
file_name_out <- file.path("data",
"mp02",
glue("atus{file_base}_0324.dat"))
if(!file.exists(file_name_out)){
url_end <- glue("atus{file_base}-0324.zip")
temp_zip <- tempfile(fileext=".zip")
temp_dir <- tools::file_path_sans_ext(temp_zip)
request("https://www.bls.gov") |>
req_url_path("tus", "datafiles", url_end) |>
req_headers(`User-Agent` = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:149.0) Gecko/20100101 Firefox/149.0") |>
req_perform(temp_zip)
unzip(temp_zip, exdir = temp_dir)
temp_file <- file.path(temp_dir, basename(file_name_out))
file.copy(temp_file, file_name_out)
}
file <- read_csv(file_name_out, show_col_types = FALSE)
# The original ALL-CAPS BLS columns are dropped; only the renamed or newly
# created (not all-caps) columns are kept.
keep_new_columns <- function(df) select(df, !matches("^[A-Z0-9_]+$", ignore.case = FALSE))
switch(file_base,
cps = file |>
rename(participant_id = TUCASEID) |>
rename_with(tolower),
resp = file |>
rename(
participant_id = TUCASEID,
survey_weight = TUFNWGTP,
time_alone = TRTALONE,
survey_year = TUYEAR
) |>
mutate(
n_children = TRCHILDNUM,
# Household composition (BLS: TRYHHCHILD = age of youngest household
# child under 18, TRSPPRES = 1 spouse present, TESPEMPNOT = 1 spouse
# employed / 2 not employed)
youngest_child_age = if_else(TRYHHCHILD >= 0, as.numeric(TRYHHCHILD), NA_real_),
spouse_present = TRSPPRES == 1,
spouse_employed = case_match(TESPEMPNOT, 1 ~ TRUE, 2 ~ FALSE, .default = NA),
# BLS definition of TELFS (labor force status)
employment_status = case_match(TELFS,
1 ~ "Employed - at work",
2 ~ "Employed - absent",
3 ~ "Unemployed - on layoff",
4 ~ "Unemployed - looking",
5 ~ "Not in labor force",
.default = NA_character_),
# Full/part time is only defined for the employed
work_time = case_match(TRDPFTPT,
1 ~ "Full time",
2 ~ "Part time",
.default = NA_character_),
# TESCHENR covers high school AND college; TESCHLVL tells them apart
enrolled_in_school = TESCHENR == 1,
enrolled_in_college = TESCHENR == 1 & TESCHLVL == 2,
# Actual diary date (the CPS month is 2 to 5 months earlier)
diary_date = ymd(TUDIARYDATE),
diary_month = month(diary_date)
) |>
keep_new_columns(),
rost = file |>
rename(participant_id = TUCASEID) |>
mutate(
sex = case_match(TESEX, 1 ~ "M", 2 ~ "F"),
roster_line = TULINENO,
roster_age = TEAGE
) |>
keep_new_columns(),
act = file |>
rename(participant_id = TUCASEID) |>
mutate(
activity_n_of_day = TUACTIVITY_N,
time_spent_min = TUACTDUR24,
start_time = TUSTARTTIM,
stop_time = TUSTOPTIME,
level1_code = paste0(TRTIER1P, "0000"),
level2_code = paste0(TRTIER2P, "00"),
# Six-digit activity code as text, so it matches the lexicon whether
# readr reads the column as text (leading zeros) or as a number
level3_code = sprintf("%06d", as.integer(TRCODEP)),
location_code = TEWHERE
) |>
keep_new_columns()
)
}
load_atus_activities <- function(){
if(!dir.exists(file.path("data", "mp02"))){
dir.create(file.path("data", "mp02"), showWarnings=FALSE, recursive=TRUE)
}
dest_file <- file.path("data", "mp02", "atus_activity_codes.csv")
if(!file.exists(dest_file)){
download.file(
"https://michael-weylandt.com/STA9750/mini/atus_activity_codes.csv",
quiet = TRUE,
destfile = dest_file,
mode = "wb"
)
}
# Six-digit activity code as text (same format as the activity file)
read_csv(dest_file, show_col_types = FALSE) |>
mutate(code_level2 = sprintf("%06d", as.integer(code_level2)),
code_level3 = sprintf("%06d", as.integer(code_level3)))
}atus_resp <- load_atus_data("resp")
atus_rost <- load_atus_data("rost")
atus_act <- load_atus_data("act")
atus_codes <- load_atus_activities()
atus_cps <- load_atus_data("cps")
# The ATUS respondent is always person line 1 (TULINENO = 1) in the ATUS files,
# so that is the row to take from the ATUS-CPS file. (PULINENO is the CPS
# household line number, which is usually 1 for the householder instead.)
cps_demo <- atus_cps |>
filter(tulineno == 1) |>
select(
participant_id,
age = prtage,
married_code = pemaritl,
fam_income = hefaminc,
race = ptdtrace,
education = peeduca,
occupation = peio1ocd,
sex = pesex
) |>
mutate(
married = case_when(
married_code %in% c(1, 2) ~ TRUE,
married_code %in% 3:6 ~ FALSE,
.default = NA
),
sex = case_match(sex, 1 ~ "M", 2 ~ "F"),
fam_income = case_match(fam_income,
1 ~ "Less than $5,000",
2 ~ "$5,000 to $7,499",
3 ~ "$7,500 to $9,999",
4 ~ "$10,000 to $12,499",
5 ~ "$12,500 to $14,999",
6 ~ "$15,000 to $19,999",
7 ~ "$20,000 to $24,999",
8 ~ "$25,000 to $29,999",
9 ~ "$30,000 to $34,999",
10 ~ "$35,000 to $39,999",
11 ~ "$40,000 to $49,999",
12 ~ "$50,000 to $59,999",
13 ~ "$60,000 to $74,999",
14 ~ "$75,000 to $99,999",
15 ~ "$100,000 to $149,999",
16 ~ "$150,000 and over",
.default = NA_character_),
education = case_match(education,
31 ~ "Less than 1st grade",
32 ~ "1st-4th grade",
33 ~ "5th or 6th grade",
34 ~ "7th or 8th grade",
35 ~ "9th grade",
36 ~ "10th grade",
37 ~ "11th grade",
38 ~ "12th grade no diploma",
39 ~ "High school diploma",
40 ~ "Some college no degree",
41 ~ "Associate's - vocational",
42 ~ "Associate's - academic",
43 ~ "Bachelor's degree",
44 ~ "Master's degree",
45 ~ "Professional degree",
46 ~ "Doctoral degree",
.default = NA_character_)
)
stopifnot(!anyDuplicated(cps_demo$participant_id))
atus_resp <- atus_resp |>
left_join(cps_demo, join_by(participant_id)) |>
mutate(
age_group = cut(age,
breaks = c(14, 24, 34, 44, 54, 64, 85),
labels = c("15-24", "25-34", "35-44",
"45-54", "55-64", "65+")),
college_age_group = cut(age,
breaks = c(17, 22, 27, 32, 40, 85),
labels = c("18-22", "23-27", "28-32",
"33-40", "41+")),
# The ATUS has no "retired" flag: ages 65+ who are out of the labor
# force is the proxy used throughout this report.
work_group = case_when(
employment_status %in% c("Employed - at work", "Employed - absent") &
work_time == "Full time" ~ "Employed full time",
employment_status == "Not in labor force" & age >= 65 ~
"Retired (65+, not in labor force)",
.default = NA_character_
)
)# Safety check: the age taken from the ATUS-CPS file must agree (within one
# year, since the CPS interview came 2 to 5 months earlier) with the age on the
# ATUS roster for the respondent. If not, the wrong person's row was linked.
roster_check <- atus_rost |>
filter(roster_line == 1) |>
select(participant_id, roster_age) |>
inner_join(atus_resp |> select(participant_id, age), join_by(participant_id)) |>
summarize(share = mean(abs(age - roster_age) <= 1, na.rm = TRUE)) |>
pull(share)
stopifnot(
"CPS demographics do not match the ATUS respondent: check the respondent row filter" =
roster_check > 0.95
)# ATUS lexicon categories used throughout. Everything is matched on the exact
# lexicon names (never on text fragments such as "care").
child_care_l2 <- c(
"Caring for and Helping Household Children",
"Activities Related to Household Children's Education",
"Activities Related to Household Children's Health"
)
# The multi-year ATUS files contain combined activity codes (for example 180782,
# "travel related to shopping", or 020681, "pet care") that the course lexicon
# file does not list. The first four digits of any code are its tier 1 + tier 2
# category, so level 1 and level 2 names are matched on those four digits.
# Level 3 names are matched on the full six digits where the code is listed.
lexicon_l2 <- atus_codes |>
mutate(code2 = substr(code_level2, 1, 4)) |>
select(code2, task_level1, task_level2) |>
distinct(code2, .keep_all = TRUE)
lexicon_l3 <- atus_codes |>
select(code_level3, task_level3) |>
distinct(code_level3, .keep_all = TRUE)
act_joined <- atus_act |>
mutate(code2 = substr(level3_code, 1, 4)) |>
left_join(lexicon_l3, join_by(level3_code == code_level3)) |>
left_join(lexicon_l2, join_by(code2)) |>
mutate(activity_label = case_when(
task_level2 %in% child_care_l2 ~ "Child Care",
task_level1 == "Household Activities" ~ "Household Activities",
task_level1 %in% c("Socializing, Relaxing, and Leisure",
"Sports, Exercise, and Recreation") ~ "Leisure & Recreation",
task_level1 == "Education" ~ "Education & Studying",
task_level1 == "Personal Care" ~ "Sleep & Personal Care",
task_level1 == "Work and Work-Related Activities" ~ "Working",
.default = NA_character_
))
# Share of diary minutes assigned to an activity category
matched_share <- sum(act_joined$time_spent_min[!is.na(act_joined$task_level1)], na.rm = TRUE) /
sum(act_joined$time_spent_min, na.rm = TRUE)
# Hours per respondent on one diary day for the activities selected by `keep`.
# Respondents who did not do the activity get 0, so means are per respondent
# per day (not per logged activity).
person_hours <- function(keep) {
act_joined |>
filter({{ keep }}) |>
group_by(participant_id) |>
summarize(hours = sum(time_spent_min, na.rm = TRUE) / 60, .groups = "drop") |>
right_join(atus_resp, join_by(participant_id)) |>
mutate(hours = replace_na(hours, 0))
}
# Weighted quantile (so quartiles are consistent with the weighted means)
wtd_quantile <- function(x, w, p) {
o <- order(x)
x <- x[o]
w <- w[o]
cw <- cumsum(w) / sum(w)
x[which(cw >= p)[1]]
}
fmt1 <- function(x) formatC(x, format = "f", digits = 1)
fmt2 <- function(x) formatC(x, format = "f", digits = 2)
say <- function(...) cat("\n", paste0(...), "\n\n", sep = "")# Q1: How many unique respondents since 2003?
n_respondents <- n_distinct(atus_resp$participant_id)
# Q2: How many different sports and exercise activities does ATUS track?
# (the "participating" list; the watching list repeats the same sports)
n_sports <- atus_codes |>
filter(task_level2 == "Participating in Sports, Exercise, or Recreation",
!str_starts(task_level3, "Other")) |>
nrow()
# Q3: Who is out of the labor force, and who is (proxy) retired?
pct_nilf <- weighted.mean(atus_resp$employment_status == "Not in labor force",
atus_resp$survey_weight, na.rm = TRUE)
pct_retired_proxy <- weighted.mean(
atus_resp$employment_status == "Not in labor force" & atus_resp$age >= 65,
atus_resp$survey_weight, na.rm = TRUE)
# Q4: Average hours of sleep per night (all respondents)
avg_sleep_hrs <- person_hours(task_level2 == "Sleeping") |>
summarize(avg = weighted.mean(hours, survey_weight)) |>
pull(avg)
# Q5: Average hours parents spend on child care (respondents with a child
# under 18 in the household)
avg_childcare_hrs <- person_hours(task_level2 %in% child_care_l2) |>
filter(n_children >= 1) |>
summarize(avg = weighted.mean(hours, survey_weight)) |>
pull(avg)The ATUS dataset is vast in scope. Since 2003, 252,808 unique Americans have participated in the survey, providing a foundation for understanding how time-spent patterns have evolved over the last two decades.
When it comes to sports, ATUS is surprisingly granular. The survey tracks 36 distinct sports and exercise activities at the most detailed activity level, allowing analysts to distinguish between activities like golf, basketball, and yoga rather than lumping them all into a single “sports” category.
About 32.2% of respondents are not in the labor force, a group that includes retirees but also students, homemakers, and people who are unable to work. Adults 65 and older who are not in the labor force, our proxy for retirees, make up 14.1% of the sample, reflecting the aging of the US population.
Sleep is a universal human activity, and the data confirms this. On average, Americans sleep 8.8 hours per night, a figure that is in line with general health recommendations.
Finally, the research finds what may be obvious to many: parenting is a substantial time commitment. Among Americans with at least one child under 18 at home, the average time devoted to child care activities, including education involvement and healthcare, is 1.02 hours per day. This figure likely understates total parenting time because it counts only primary activities; supervising children while doing something else is not included.
“The grass is greener”… a common assumption is that retirees, with all of the extra time freed up from their careers, are able to dedicate it to activities that felt out of reach during their working years. One such activity is lawn care and gardening. The table below puts this assumption to the test by comparing average time spent on these activities between retired and full-time employed Americans. Both groups are averaged over every respondent, including those who did no lawn care on their diary day.
lawn_care <- person_hours(task_level3 == "Lawn, garden, and houseplant care") |>
filter(!is.na(work_group)) |>
group_by(work_group) |>
summarize(
respondents = n(),
avg_min_per_day = weighted.mean(hours, survey_weight) * 60,
pct_any = weighted.mean(hours > 0, survey_weight),
avg_min_if_any = weighted.mean(hours[hours > 0], survey_weight[hours > 0]) * 60,
.groups = "drop"
) |>
mutate(avg_hrs_per_week = avg_min_per_day * 7 / 60)
lawn_care |>
gt() |>
cols_label(
work_group = "Group",
respondents = "Respondents",
avg_min_per_day = "Avg. Minutes/Day (All)",
pct_any = "% Doing Any",
avg_min_if_any = "Avg. Minutes (If Any)",
avg_hrs_per_week = "Avg. Hours/Week (All)"
) |>
fmt_number(columns = c(avg_min_per_day, avg_min_if_any), decimals = 1) |>
fmt_number(columns = avg_hrs_per_week, decimals = 2) |>
fmt_number(columns = respondents, decimals = 0, use_seps = TRUE) |>
fmt_percent(columns = pct_any, decimals = 1) |>
tab_header(
title = "Time Spent on Lawn Care & Gardening",
subtitle = "Retired (proxy) vs. Full-Time Employed Americans"
) |>
tab_source_note("Source: ATUS 2003–2024 Public Use Microdata")| Time Spent on Lawn Care & Gardening | |||||
| Retired (proxy) vs. Full-Time Employed Americans | |||||
| Group | Respondents | Avg. Minutes/Day (All) | % Doing Any | Avg. Minutes (If Any) | Avg. Hours/Week (All) |
|---|---|---|---|---|---|
| Employed full time | 122,728 | 9.0 | 7.6% | 119.0 | 1.06 |
| Retired (65+, not in labor force) | 43,911 | 21.6 | 17.8% | 121.6 | 2.52 |
| Source: ATUS 2003–2024 Public Use Microdata | |||||
Retirees do spend more time on lawn care and gardening than their full-time employed counterparts (21.6 vs. 9.0 minutes per day; 18% vs. 8% report any lawn or garden time). The common belief holds up: some of the free time that retirement creates goes to the yard.
Understanding how affluent Americans allocate their time offers insight into the lifestyle trade-offs that come with high-income careers. Here, “high-earning” is defined as households with annual family income of at least $100,000 (nominal dollars, not adjusted for inflation across the survey years). The table ranks their top eight activity categories by average daily time spent.
high_resp <- atus_resp |>
filter(fam_income %in% c("$100,000 to $149,999", "$150,000 and over"))
high_total_w <- sum(high_resp$survey_weight)
high_all <- act_joined |>
filter(task_level1 != "Data Codes") |>
inner_join(high_resp |> select(participant_id, survey_weight),
join_by(participant_id)) |>
group_by(task_level1) |>
summarize(avg_min_per_day = sum(time_spent_min * survey_weight) / high_total_w,
.groups = "drop") |>
mutate(avg_hrs_per_day = avg_min_per_day / 60) |>
arrange(desc(avg_min_per_day))
high_earner_time <- high_all |> slice_head(n = 8)
high_earner_time |>
gt() |>
cols_label(
task_level1 = "Activity Category",
avg_min_per_day = "Avg. Minutes/Day",
avg_hrs_per_day = "Avg. Hours/Day"
) |>
fmt_number(columns = avg_min_per_day, decimals = 1) |>
fmt_number(columns = avg_hrs_per_day, decimals = 2) |>
tab_header(
title = "Where High-Earning Americans Spend Their Time",
subtitle = "Households earning $100,000 or more annually"
) |>
tab_source_note("Source: ATUS 2003–2024 Public Use Microdata")| Where High-Earning Americans Spend Their Time | ||
| Households earning $100,000 or more annually | ||
| Activity Category | Avg. Minutes/Day | Avg. Hours/Day |
|---|---|---|
| Personal Care | 559.0 | 9.32 |
| Work and Work-Related Activities | 241.5 | 4.02 |
| Socializing, Relaxing, and Leisure | 235.3 | 3.92 |
| Household Activities | 104.2 | 1.74 |
| Traveling | 77.3 | 1.29 |
| Eating and Drinking | 71.5 | 1.19 |
| Caring for and Helping Household Members | 30.2 | 0.50 |
| Education | 29.7 | 0.50 |
| Source: ATUS 2003–2024 Public Use Microdata | ||
The largest category is personal care at 9.3 hours per day, followed by work and work-related activities (4.0) and socializing, relaxing, and leisure (3.9).
Work and work-related activities average 4.0 hours per day. That is an average over every diary day, weekends included, and over every respondent in these households, including those who were not working, so it is lower than the length of a typical workday. High earners also average 3.9 hours per day on socializing, relaxing, and leisure, about as much as they spend on work. High earners do not give up all of their free time for professional output.
Americans who find themselves aboard a bus, train, subway, boat, or ferry occupy a unique moment where their physical movement is fixed but their attention is entirely their own. The table below identifies the ten most commonly reported activities among Americans while aboard these vehicles, other than the travel itself. The ATUS codes the ride as travel, so riding is excluded here.
# ATUS "where" codes: 15 = bus, 16 = subway/train, 18 = boat/ferry
transit_codes <- c(15, 16, 18)
transit_activities <- act_joined |>
filter(location_code %in% transit_codes,
!task_level1 %in% c("Traveling", "Data Codes"),
!str_detect(tolower(coalesce(task_level3, "")), "boat")) |>
inner_join(atus_resp |> select(participant_id, survey_weight),
join_by(participant_id)) |>
# Combined multi-year codes have no tier-3 name; fall back to the tier-2 name
mutate(activity = coalesce(task_level3, task_level2)) |>
group_by(activity) |>
summarize(
n_people = n_distinct(participant_id),
avg_min = weighted.mean(time_spent_min, survey_weight, na.rm = TRUE),
.groups = "drop"
) |>
arrange(desc(n_people)) |>
slice_head(n = 10)
transit_activities |>
gt() |>
cols_label(
activity = "Activity",
n_people = "Number of Respondents",
avg_min = "Avg. Minutes"
) |>
fmt_number(columns = avg_min, decimals = 1) |>
fmt_number(columns = n_people, decimals = 0) |>
tab_header(
title = "Most Common Activities on a Bus, Train, Subway, Boat, or Ferry",
subtitle = "Primary activities other than travel, reported while aboard these vehicles"
) |>
tab_source_note("Source: ATUS 2003–2024 Public Use Microdata")| Most Common Activities on a Bus, Train, Subway, Boat, or Ferry | ||
| Primary activities other than travel, reported while aboard these vehicles | ||
| Activity | Number of Respondents | Avg. Minutes |
|---|---|---|
| Work, main job | 63 | 101.4 |
| Eating and drinking | 60 | 34.7 |
| Fishing | 33 | 238.7 |
| Socializing and communicating with others | 33 | 61.1 |
| Household and personal organization and planning | 20 | 29.9 |
| Picking up or dropping off household adult | 15 | 2.8 |
| Picking up or dropping off household children | 12 | 2.5 |
| Relaxing, thinking | 12 | 102.9 |
| Shopping, except groceries, food, and gas | 12 | 52.8 |
| Other Arts and entertainment | 11 | 119.6 |
| Source: ATUS 2003–2024 Public Use Microdata | ||
Because the ATUS codes the ride itself as travel, this table cannot show how riders use their trip. It shows the other primary activities respondents logged while aboard. The most commonly reported is work, main job (63 respondents, averaging 101.4 minutes), followed by eating and drinking (60) and fishing (33). Work appears in the list, and many of those entries are likely drivers, conductors, and crew for whom being aboard is the job; the ATUS does not identify them.
For our app, the takeaway is a limit rather than an opportunity: the ATUS cannot tell us whether transit time is a good moment to prompt users to log their activities. That would need to be tested with app usage data.
The table below tracks four universally common activities:
We split the results across six age groups to reveal how priorities shift as Americans grow older. Each number is an average over every respondent in the age group, including people who did none of the activity on their diary day.
fav_activities <- bind_rows(
person_hours(task_level2 == "Sleeping") |>
mutate(activity = "Sleep"),
person_hours(task_level2 == "Eating and Drinking") |>
mutate(activity = "Eating & Drinking"),
person_hours(task_level2 == "Participating in Sports, Exercise, or Recreation") |>
mutate(activity = "Sports & Exercise"),
person_hours(task_level3 == "Television and movies (not religious)") |>
mutate(activity = "TV Watching")
) |>
filter(!is.na(age_group)) |>
group_by(activity, age_group) |>
summarize(
avg_hrs = round(weighted.mean(hours, survey_weight), 2),
.groups = "drop"
)
fav_activities |>
pivot_wider(names_from = age_group, values_from = avg_hrs) |>
gt() |>
cols_label(activity = "Activity") |>
tab_spanner(columns = -activity, label = "Age Group") |>
tab_header(
title = "Daily Hours Spent on Selected Activities by Age Group",
subtitle = "Sleep, Eating & Drinking, Sports & Exercise, and TV Watching"
) |>
tab_source_note("Source: ATUS 2003–2024 Public Use Microdata")| Daily Hours Spent on Selected Activities by Age Group | ||||||
| Sleep, Eating & Drinking, Sports & Exercise, and TV Watching | ||||||
| Activity |
Age Group
|
|||||
|---|---|---|---|---|---|---|
| 15-24 | 25-34 | 35-44 | 45-54 | 55-64 | 65+ | |
| Eating & Drinking | 0.96 | 1.06 | 1.06 | 1.07 | 1.15 | 1.29 |
| Sleep | 9.41 | 8.73 | 8.49 | 8.44 | 8.53 | 8.97 |
| Sports & Exercise | 0.51 | 0.28 | 0.26 | 0.24 | 0.25 | 0.25 |
| TV Watching | 2.17 | 2.12 | 2.15 | 2.55 | 3.16 | 4.20 |
| Source: ATUS 2003–2024 Public Use Microdata | ||||||
Sleep varies across the lifespan, between 8.44 and 9.41 hours per day. The youngest and oldest Americans sleep slightly more than those in the middle years, which likely reflects the demands of work and family that peak during those years.
Eating and drinking time grows with age, from 0.96 hours among 15 to 24 year olds to 1.29 hours among those 65 and older, perhaps reflecting a slower, more social approach to meals in retirement. Sports and exercise average 0.51 hours at their peak (15-24) and 0.25 hours among those 65 and older.
TV watching rises from 2.17 hours among the youngest group to 4.20 hours among Americans 65 and older, a change of 94%, consistent with retirement freeing up time for leisure, although these snapshots cannot separate aging from generational differences.
household_by_age <- person_hours(task_level1 == "Household Activities") |>
filter(!is.na(age_group)) |>
group_by(age_group) |>
summarize(
avg_hrs = weighted.mean(hours, survey_weight),
.groups = "drop"
)
ggplot(household_by_age, aes(x = age_group, y = avg_hrs, group = 1)) +
geom_line(color = "#0072B2", linewidth = 1.2) +
geom_point(color = "#0072B2", size = 3) +
scale_y_continuous(limits = c(0, NA)) +
labs(
title = "Time Spent on Household Activities by Age Group",
subtitle = "Average hours per day, all respondents in each age group",
x = "Age Group",
y = "Average Hours per Day",
caption = "Source: ATUS 2003–2024 Public Use Microdata"
)Time spent on household activities is highest among 65+ (2.53 hours per day) and lowest among 15-24 (0.92). It rises steadily with age, which likely reflects a shift away from paid work toward home-based responsibilities and, for retirees, having more hours available during the day to devote to these tasks.
Do teachers really spend their summers at the beach? The chart compares a typical day for K-12 and other teachers in the summer (June to August) with a day in the school year (September to May). Both include weekends and holidays. Months come from the actual diary date, and teachers are identified from Census occupation codes 2300 to 2360, a range that was revised during the survey period, so we treat the results as indicative.
teachers <- atus_resp |>
filter(!is.na(occupation), occupation >= 2300, occupation <= 2360,
employment_status %in% c("Employed - at work", "Employed - absent"),
!is.na(diary_month)) |>
mutate(period = if_else(diary_month %in% 6:8,
"Summer (June-August)", "School year (September-May)"))
teacher_w <- teachers |>
group_by(period) |>
summarize(w_total = sum(survey_weight), n = n(), .groups = "drop")
teacher_time <- act_joined |>
filter(task_level1 != "Data Codes") |>
inner_join(teachers |> select(participant_id, survey_weight, period),
join_by(participant_id)) |>
group_by(period, task_level1) |>
summarize(w_minutes = sum(time_spent_min * survey_weight), .groups = "drop") |>
left_join(teacher_w, join_by(period)) |>
mutate(avg_hrs = w_minutes / w_total / 60)
top_teacher_cats <- teacher_time |>
group_by(task_level1) |>
summarize(m = mean(avg_hrs), .groups = "drop") |>
slice_max(m, n = 6) |>
pull(task_level1)
teacher_caption <- paste0(
"n = ", paste0(teacher_w$n, " (", teacher_w$period, ")", collapse = "; "),
"\nSource: ATUS 2003–2024 Public Use Microdata")
teacher_time |>
filter(task_level1 %in% top_teacher_cats) |>
mutate(task_level1 = str_wrap(task_level1, 28)) |>
ggplot(aes(x = reorder(task_level1, avg_hrs), y = avg_hrs, fill = period)) +
geom_col(position = "dodge") +
coord_flip() +
scale_fill_manual(values = c("Summer (June-August)" = "#956C3B",
"School year (September-May)" = "#0072B2")) +
labs(
title = "How Teachers' Days Differ in Summer vs. the School Year",
subtitle = "Average hours per day on the six largest activity categories",
x = NULL,
y = "Average Hours per Day",
fill = NULL,
caption = teacher_caption
) +
theme(legend.position = "bottom")Work and work-related activities average 3.31 hours per day in the summer and 5.14 in the school year (1752 and 5740 teacher diaries). Work falls in the summer but remains substantial, suggesting that many teachers use the summer months for professional development, lesson planning, or even second jobs rather than taking an extended break from work. This has implications for our app: educators may benefit from features that help them manage their time during the summer, rather than assuming they have more free time to log leisure activities.
college_resp <- atus_resp |>
filter(enrolled_in_college, !is.na(college_age_group))
college_w <- college_resp |>
group_by(college_age_group) |>
summarize(w_total = sum(survey_weight), n = n(), .groups = "drop")
college_edu <- act_joined |>
filter(task_level1 == "Education") |>
inner_join(college_resp |> select(participant_id, survey_weight, college_age_group),
join_by(participant_id)) |>
group_by(college_age_group, task_level2) |>
summarize(w_minutes = sum(time_spent_min * survey_weight), .groups = "drop") |>
left_join(college_w, join_by(college_age_group)) |>
mutate(
avg_hrs = w_minutes / w_total / 60,
age_label = paste0(college_age_group, "\n(n = ", comma(n), ")")
)
age_label_levels <- college_w |>
arrange(college_age_group) |>
mutate(lbl = paste0(college_age_group, "\n(n = ", comma(n), ")")) |>
pull(lbl)
college_edu <- college_edu |>
mutate(age_label = factor(age_label, levels = age_label_levels))
college_totals <- college_edu |>
group_by(college_age_group) |>
summarize(total_hrs = sum(avg_hrs), n = first(n), .groups = "drop")
ggplot(college_edu, aes(x = age_label, y = avg_hrs, fill = task_level2)) +
geom_col(position = "stack") +
scale_fill_brewer(type = "qual", palette = 2, name = "Activity Type",
labels = function(x) str_wrap(x, width = 25)) +
labs(
title = "Educational Activity Time by Age Among College Students",
subtitle = "Average hours per day, all enrolled college students in each age group",
x = "Age Group",
y = "Average Hours per Day",
caption = "Source: ATUS 2003–2024 Public Use Microdata"
) +
theme(
legend.position = "bottom",
legend.text = element_text(size = 7),
legend.key.size = unit(0.4, "cm"),
plot.margin = margin(5, 5, 20, 5)
) +
guides(fill = guide_legend(nrow = 3))Younger college students (ages 18 to 22) spend more total time on educational activities than their older peers: 2.71 hours per day, compared with 2.17 for students 23 and older. This may reflect that traditional-age students are more likely to be enrolled full-time, while older students often balance coursework with work and family obligations.
sports_person <- act_joined |>
filter(task_level2 %in% c("Participating in Sports, Exercise, or Recreation",
"Attending Sports or Recreational Events"),
!is.na(task_level3),
!str_detect(task_level3, "^Other"),
!str_detect(tolower(task_level3), "vehicle touring")) |>
mutate(
# Strip action prefix to group watching + participating together
sport_name = str_remove(tolower(task_level3),
"^watching |^playing |^doing |^participating in "),
# Clean up a few names for display
sport_name = case_match(sport_name,
"skiing, ice skating, snowboarding" ~ "Skiing/Skating",
"climbing, spelunking, caving" ~ "Climbing/Caving",
"weightlifting or strength training" ~ "Weightlifting",
"using cardiovascular equipment" ~ "Cardio Equipment",
"working out, unspecified" ~ "Working Out",
"people working out, unspecified" ~ "Working Out",
"rodeo competitions" ~ "Rodeo",
"equestrian sports" ~ "Equestrian",
"water sports" ~ "Water Sports",
"racquet sports" ~ "Racquet Sports",
"martial arts" ~ "Martial Arts",
.default = str_to_title(sport_name)
)
) |>
group_by(participant_id, sport_name) |>
summarize(minutes = sum(time_spent_min, na.rm = TRUE), .groups = "drop")
# Average minutes per respondent per day: divide total weighted minutes by the
# total weight of ALL respondents in the age group (so non-participants count as 0)
age_totals <- atus_resp |>
filter(!is.na(age_group)) |>
group_by(age_group) |>
summarize(w_total = sum(survey_weight), .groups = "drop")
sports_ranked <- sports_person |>
inner_join(atus_resp |> select(participant_id, survey_weight, age_group),
join_by(participant_id)) |>
filter(!is.na(age_group)) |>
group_by(age_group, sport_name) |>
summarize(w_minutes = sum(minutes * survey_weight), .groups = "drop") |>
left_join(age_totals, join_by(age_group)) |>
mutate(avg_min = w_minutes / w_total) |>
group_by(age_group) |>
mutate(rank = rank(-avg_min, ties.method = "first")) |>
ungroup()
sports_by_age <- sports_ranked |>
filter(rank <= 6) |>
group_by(sport_name) |>
filter(n() >= 3) |>
ungroup()
# Label each line where it last appears
sport_labels <- sports_by_age |>
group_by(sport_name) |>
slice_max(as.integer(age_group), n = 1, with_ties = FALSE) |>
ungroup()
# Safe, distinct color palette
safe_colors <- c("#0072B2", "#D55E00", "#009E73", "#956C3B",
"#CC79A7", "#8B0000", "#4B0082", "#2F4F4F",
"#556B2F", "#8B4513", "#483D8B", "#8B7355")
ggplot(sports_by_age, aes(x = age_group, y = rank,
color = sport_name, group = sport_name)) +
geom_bump(linewidth = 1) +
geom_point(size = 3) +
geom_label_repel(
data = sport_labels,
aes(label = sport_name),
hjust = 0,
nudge_x = 0.5,
size = 2.8,
direction = "y",
segment.size = 0.3,
box.padding = 0.6,
force = 8,
fill = "white",
label.size = 0.2
) +
scale_y_reverse(breaks = 1:6) +
scale_color_manual(values = colorRampPalette(safe_colors)(12)) +
coord_cartesian(clip = "off") +
theme_bw() +
theme(
legend.position = "none",
axis.text.x = element_text(angle = 15, hjust = 1),
plot.margin = margin(5, 160, 5, 5)
) +
labs(
title = "Sports and Exercise Ranked by Time Spent, by Age Group",
subtitle = "Ranking by average minutes per person per day | Participating and watching combined",
x = "Age Group",
y = "Rank (1 = Most Time Spent)",
caption = "Source: ATUS 2003–2024 Public Use Microdata"
)The bump plot reveals how sports and exercise shift across the American lifespan. Each ranking uses average minutes per person per day, counting everyone in the age group, so it reflects both how many people do an activity and how long they spend on it. The top activity in each age group is: 15-24: basketball; 25-34: walking; 35-44: walking; 45-54: walking; 55-64: walking; and 65+: walking. Walking, Water Sports and Working Out rank in the top six for every age group.
Because each ranking averages over every diary day of the year, seasonal sports are averaged over many off-season days, which pulls them down relative to year-round activities. This helps explain why baseball, often called America’s pastime, does not make the top six in any age group (its best rank is 10), and why hockey and skiing/skating do not appear either.
child_time <- person_hours(task_level2 %in% child_care_l2) |>
filter(n_children >= 1) |>
mutate(hours_per_child = hours / n_children)
childcare_by_family <- child_time |>
filter(n_children <= 6) |>
group_by(n_children) |>
summarize(
n = n(),
total_hrs = weighted.mean(hours, survey_weight),
avg_hrs_per_child = weighted.mean(hours_per_child, survey_weight),
.groups = "drop"
)
childcare_by_family |>
mutate(family_label = paste0(n_children, "\n(n = ", comma(n), ")")) |>
select(family_label, `Total child care (hours per day)` = total_hrs,
`Per child (hours per day)` = avg_hrs_per_child) |>
pivot_longer(-family_label, names_to = "measure", values_to = "hours") |>
mutate(measure = factor(measure, levels = c("Total child care (hours per day)",
"Per child (hours per day)"))) |>
ggplot(aes(x = family_label, y = hours)) +
geom_col(fill = "#D55E00") +
facet_wrap(~ measure) +
labs(
title = "Parental Child Care Time by Family Size",
subtitle = "Total care time rises with family size more slowly than the number of children",
x = "Number of Children in Household",
y = "Average Hours per Day",
caption = "Source: ATUS 2003–2024 Public Use Microdata"
)As family size increases, the time parents can devote to each individual child drops: parents with one child spend 0.75 hours per child per day, compared with 0.40 for parents of three or more, or 1.9 times as much. Total child care time rises, from 0.75 to 1.32 hours per day, but not in proportion to the number of children. This is consistent with parental attention being a finite resource that gets divided as families grow, the idea behind “middle-child syndrome”. The data cannot show whether any particular child, such as a middle child, receives less than their siblings.
For each target group, the table shows the weighted average and the middle half of respondents (Q1 to Q3), averaged over everyone in the group, including those who did none of the activity. Each target is either a minimum (“at least”) or a limit (“at most”), set near what peers in the group actually do. The last column is the share of the group that meets it.
# `targets` is a tibble with one row per activity: activity (an activity_label),
# target_hrs, and direction ("at least", "at most", or "none" for no target)
build_profile <- function(group_df, targets) {
activities <- targets$activity
totals <- act_joined |>
filter(activity_label %in% activities) |>
inner_join(group_df |> select(participant_id), join_by(participant_id)) |>
group_by(participant_id, activity_label) |>
summarize(hrs = sum(time_spent_min, na.rm = TRUE) / 60, .groups = "drop")
group_df |>
select(participant_id, survey_weight) |>
cross_join(targets |> rename(activity_label = activity)) |>
left_join(totals, join_by(participant_id, activity_label)) |>
mutate(
hrs = replace_na(hrs, 0),
meets = case_when(
direction == "at least" ~ hrs >= target_hrs,
direction == "at most" ~ hrs <= target_hrs,
.default = NA
)
) |>
group_by(activity_label, target_hrs, direction) |>
summarize(
low = wtd_quantile(hrs, survey_weight, 0.25),
avg_hrs = weighted.mean(hrs, survey_weight),
high = wtd_quantile(hrs, survey_weight, 0.75),
pct_meeting = weighted.mean(meets, survey_weight),
.groups = "drop"
) |>
mutate(activity_label = factor(activity_label, levels = activities)) |>
arrange(activity_label) |>
mutate(
activity = as.character(activity_label),
goal = if_else(direction == "none", "None",
paste(direction, fmt1(target_hrs))),
# Only a miss in the target's direction counts as a shortfall
shortfall = case_when(
direction == "at least" ~ pmax(target_hrs - avg_hrs, 0),
direction == "at most" ~ pmax(avg_hrs - target_hrs, 0),
.default = NA_real_
)
) |>
select(activity, low, avg_hrs, high, target_hrs, direction, goal,
pct_meeting, shortfall)
}
profile_gt <- function(tbl, title, subtitle) {
tbl |>
select(activity, low, avg_hrs, high, goal, pct_meeting) |>
gt() |>
cols_label(
activity = "Activity",
low = "Low (Q1 hrs)",
avg_hrs = "Average (hrs/day)",
high = "High (Q3 hrs)",
goal = "Target (hrs/day)",
pct_meeting = "% Meeting Target"
) |>
fmt_number(columns = c(low, avg_hrs, high), decimals = 1) |>
fmt_percent(columns = pct_meeting, decimals = 0) |>
sub_missing(columns = pct_meeting, missing_text = "n/a") |>
tab_header(title = title, subtitle = subtitle) |>
tab_source_note("Source: ATUS 2003–2024 Public Use Microdata") |>
tab_footnote(
footnote = "Quartiles are taken over everyone in the group, including people who did none of the activity that day, so Q3 can be 0.0 while the average is above 0 when most of the group did none.",
locations = cells_column_labels(columns = high)
) |>
tab_footnote(
footnote = "Targets are suggested goals for the app's gamification system, based on product manager judgment. \"At least\" targets are minimums and \"at most\" targets are limits. Activities with no target show the group average only.",
locations = cells_column_labels(columns = goal)
)
}
# Plain-language summary of a profile table
profile_text <- function(tbl, who, n) {
desc <- glue_collapse(
glue("time on {tolower(tbl$activity)} averages {fmt1(tbl$avg_hrs)} hours",
"{ifelse(tbl$direction == 'none', '', paste0(' (target: ', tbl$goal, ')'))}"),
sep = ", ", last = ", and ")
short <- tbl |> filter(!is.na(shortfall), shortfall > 0)
gap_txt <- if (nrow(short) == 0) {
"On average, the group already meets all of its targets."
} else {
big <- short |> slice_max(shortfall, n = 1, with_ties = FALSE)
paste0("The largest shortfall against a target is for ", tolower(big$activity),
", where the average of ", fmt1(big$avg_hrs), " hours is ", fmt1(big$shortfall),
" hours ", if (big$direction == "at least") "below" else "above",
" the target (", big$goal, "), and ", percent(big$pct_meeting, accuracy = 1),
" of the group meets it.")
}
paste0("Among ", who, " (n = ", comma(n), "), ", desc, ". ", gap_txt)
}# ── Demographic 1: Stay-at-home dads (proxy) ─────────────────────────────────
# The ATUS does not identify stay-at-home parents directly. Base proxy: married
# men under 65 with a child under 18 who are not in the labor force. Tighter
# proxy: also an employed spouse in the household and a youngest child under 13.
sahd_base <- atus_resp |>
filter(sex == "M", married %in% TRUE, n_children >= 1,
employment_status == "Not in labor force",
age >= 18, age < 65)
sahd_tight <- sahd_base |>
filter(spouse_present %in% TRUE, spouse_employed %in% TRUE,
youngest_child_age < 13)
# Use the tighter definition unless it leaves too few respondents
use_tight <- nrow(sahd_tight) >= 300
sahd <- if (use_tight) sahd_tight else sahd_base
sahd_definition <- if (use_tight) {
"married men under 65 with an employed spouse and a youngest child under 13 who are not in the labor force"
} else {
"married men under 65 with a child under 18 who are not in the labor force"
}
sahd_targets <- tribble(
~activity, ~target_hrs, ~direction,
"Child Care", 2.0, "at least",
"Household Activities", 3.0, "at least",
"Leisure & Recreation", 5.0, "at most"
)
sahd_profile <- build_profile(sahd, sahd_targets)
profile_gt(sahd_profile,
title = "Time Use Profile: Stay-at-Home Dads (Proxy)",
subtitle = glue("{str_to_sentence(sahd_definition)} (n = {comma(nrow(sahd))})"))| Time Use Profile: Stay-at-Home Dads (Proxy) | |||||
| Married men under 65 with an employed spouse and a youngest child under 13 who are not in the labor force (n = 767) | |||||
| Activity | Low (Q1 hrs) | Average (hrs/day) | High (Q3 hrs)1 | Target (hrs/day)2 | % Meeting Target |
|---|---|---|---|---|---|
| Child Care | 0.0 | 1.8 | 2.8 | at least 2.0 | 37% |
| Household Activities | 0.4 | 2.4 | 3.8 | at least 3.0 | 34% |
| Leisure & Recreation | 3.3 | 6.1 | 8.4 | at most 5.0 | 46% |
| 1 Quartiles are taken over everyone in the group, including people who did none of the activity that day, so Q3 can be 0.0 while the average is above 0 when most of the group did none. | |||||
| 2 Targets are suggested goals for the app's gamification system, based on product manager judgment. "At least" targets are minimums and "at most" targets are limits. Activities with no target show the group average only. | |||||
| Source: ATUS 2003–2024 Public Use Microdata | |||||
Stay-at-home dads represent a relatively small demographic in the United States. The ATUS does not identify them directly, so this group is approximated as married men under 65 with an employed spouse and a youngest child under 13 who are not in the labor force; it also includes men who are out of the labor force for other reasons, such as disability or school.
Among this group (n = 767), time on child care averages 1.8 hours (target: at least 2.0), time on household activities averages 2.4 hours (target: at least 3.0), and time on leisure & recreation averages 6.1 hours (target: at most 5.0). The largest shortfall against a target is for leisure & recreation, where the average of 6.1 hours is 1.1 hours above the target (at most 5.0), and 46% of the group meets it.
# ── Demographic 2: College undergraduates ────────────────────────────────────
# Definition: Age 18-24, enrolled in college (not high school)
college_students <- atus_resp |>
filter(age >= 18, age <= 24, enrolled_in_college)
# Working is not treated as a problem to fix, so it has no target
college_targets <- tribble(
~activity, ~target_hrs, ~direction,
"Education & Studying", 5.0, "at least",
"Leisure & Recreation", 3.0, "at most",
"Sleep & Personal Care", 8.0, "at least",
"Working", NA_real_, "none"
)
college_profile <- build_profile(college_students, college_targets)
profile_gt(college_profile,
title = "Time Use Profile: College Undergraduates",
subtitle = glue("Enrolled students ages 18–24 (n = {comma(nrow(college_students))})"))| Time Use Profile: College Undergraduates | |||||
| Enrolled students ages 18–24 (n = 5,213) | |||||
| Activity | Low (Q1 hrs) | Average (hrs/day) | High (Q3 hrs)1 | Target (hrs/day)2 | % Meeting Target |
|---|---|---|---|---|---|
| Education & Studying | 0.0 | 2.7 | 4.8 | at least 5.0 | 25% |
| Leisure & Recreation | 2.0 | 4.6 | 6.5 | at most 3.0 | 38% |
| Sleep & Personal Care | 8.5 | 10.0 | 11.5 | at least 8.0 | 84% |
| Working | 0.0 | 2.2 | 4.7 | None | n/a |
| 1 Quartiles are taken over everyone in the group, including people who did none of the activity that day, so Q3 can be 0.0 while the average is above 0 when most of the group did none. | |||||
| 2 Targets are suggested goals for the app's gamification system, based on product manager judgment. "At least" targets are minimums and "at most" targets are limits. Activities with no target show the group average only. | |||||
| Source: ATUS 2003–2024 Public Use Microdata | |||||
College undergraduates face a unique time allocation challenge: balancing academic demands with social life, work, and adequate rest.
Among enrolled college students ages 18 to 24 (n = 5,213), time on education & studying averages 2.7 hours (target: at least 5.0), time on leisure & recreation averages 4.6 hours (target: at most 3.0), time on sleep & personal care averages 10.0 hours (target: at least 8.0), and time on working averages 2.2 hours. The largest shortfall against a target is for education & studying, where the average of 2.7 hours is 2.3 hours below the target (at least 5.0), and 25% of the group meets it.
# ── Demographic 3: Career-Changing Graduate Students ─────────────────────────
# Married adults 30-40, enrolled in college, no children at home, working
# part-time or not working (unemployed or out of the labor force)
career_changers <- atus_resp |>
filter(age >= 30, age <= 40, enrolled_in_college, married %in% TRUE,
n_children == 0,
(employment_status %in% c("Employed - at work", "Employed - absent") &
work_time == "Part time") |
employment_status %in% c("Unemployed - on layoff",
"Unemployed - looking",
"Not in labor force"))
career_targets <- tribble(
~activity, ~target_hrs, ~direction,
"Education & Studying", 5.0, "at least",
"Working", 2.0, "at most",
"Leisure & Recreation", 3.0, "at most",
"Sleep & Personal Care", 8.0, "at least",
"Household Activities", 1.5, "at most"
)
career_profile <- build_profile(career_changers, career_targets)
profile_gt(career_profile,
title = "Time Use Profile: Career-Changing Graduate Students",
subtitle = glue("Married adults ages 30–40, enrolled in college, no children, working part-time or not at all (n = {comma(nrow(career_changers))})"))| Time Use Profile: Career-Changing Graduate Students | |||||
| Married adults ages 30–40, enrolled in college, no children, working part-time or not at all (n = 102) | |||||
| Activity | Low (Q1 hrs) | Average (hrs/day) | High (Q3 hrs)1 | Target (hrs/day)2 | % Meeting Target |
|---|---|---|---|---|---|
| Education & Studying | 0.0 | 3.8 | 6.8 | at least 5.0 | 32% |
| Working | 0.0 | 1.1 | 0.0 | at most 2.0 | 80% |
| Leisure & Recreation | 2.0 | 4.1 | 5.2 | at most 3.0 | 45% |
| Sleep & Personal Care | 8.0 | 9.5 | 10.7 | at least 8.0 | 77% |
| Household Activities | 0.5 | 1.6 | 2.3 | at most 1.5 | 64% |
| 1 Quartiles are taken over everyone in the group, including people who did none of the activity that day, so Q3 can be 0.0 while the average is above 0 when most of the group did none. | |||||
| 2 Targets are suggested goals for the app's gamification system, based on product manager judgment. "At least" targets are minimums and "at most" targets are limits. Activities with no target show the group average only. | |||||
| Source: ATUS 2003–2024 Public Use Microdata | |||||
Career-changing graduate students occupy a uniquely demanding position: unlike traditional students, they bring years of professional experience into the classroom while simultaneously managing the responsibilities of adult life. This group is small in the ATUS (n = 102), so these numbers are indicative rather than precise.
Among career-changing graduate students (n = 102), time on education & studying averages 3.8 hours (target: at least 5.0), time on working averages 1.1 hours (target: at most 2.0), time on leisure & recreation averages 4.1 hours (target: at most 3.0), time on sleep & personal care averages 9.5 hours (target: at least 8.0), and time on household activities averages 1.6 hours (target: at most 1.5). The largest shortfall against a target is for education & studying, where the average of 3.8 hours is 1.2 hours below the target (at least 5.0), and 32% of the group meets it.
Their average of 3.8 hours per day on education is more than the 2.7 hours of traditional undergraduates. The largest shortfall, for education & studying, is the clearest opportunity for “gamification” to make a meaningful difference.
# ── Data setup ────────────────────────────────────────────────────────────────
# "Peer Average" comes from the career-changer profile computed above;
# "My Week" is my own estimated week.
ring_order <- c("Education & Studying", "Working", "Leisure & Recreation",
"Sleep & Personal Care", "Household Activities")
activities <- c("Education &\nStudying", "Working &\nWork-Related",
"Leisure &\nRecreation", "Sleep &\nPersonal Care",
"Household\nActivities")
target_hrs <- career_profile$target_hrs[match(ring_order, career_profile$activity)]
typical_hrs <- career_profile$avg_hrs[match(ring_order, career_profile$activity)]
my_hrs <- c(8.0, 1.0, 2.0, 8.0, 3.0)
target_pct_typical <- pmin(typical_hrs / target_hrs, 1)
target_pct_mine <- pmin(my_hrs / target_hrs, 1)
n <- length(activities)
rings_data <- data.frame(
activity = rep(factor(activities, levels = rev(activities)), 2),
group = factor(rep(c("Peer Average", "My Week"), each = n),
levels = c("Peer Average", "My Week")),
pct = c(target_pct_typical, target_pct_mine),
actual_hrs = round(c(typical_hrs, my_hrs), 1),
target_hrs = rep(target_hrs, 2),
ring = rep(seq(n, 1), 2)
)
ring_colors <- c(
"Education &\nStudying" = "#0072B2",
"Working &\nWork-Related" = "#D55E00",
"Leisure &\nRecreation" = "#009E73",
"Sleep &\nPersonal Care" = "#956C3B",
"Household\nActivities" = "#CC79A7"
)
ggplot(rings_data) +
# Background track
geom_arc_bar(
aes(x0 = 0, y0 = 0, r0 = ring - 0.4, r = ring,
start = 0, end = 2 * pi),
fill = "#EEEEEE", color = NA
) +
# Filled arcs
geom_arc_bar(
aes(x0 = 0, y0 = 0, r0 = ring - 0.4, r = ring,
start = -pi/2, end = -pi/2 + 2 * pi * pct,
fill = activity),
color = NA
) +
# Hours label at the bottom of each ring
geom_text(
data = rings_data |> filter(group == "My Week"),
aes(x = 0, y = -ring + 0.2,
label = paste0(actual_hrs, "h"),
color = activity),
size = 2.8, fontface = "bold", hjust = 0.5
) +
scale_fill_manual(
values = ring_colors,
name = NULL,
labels = c(
"Education &\nStudying" = "📚 Education & Studying",
"Working &\nWork-Related" = "💼 Working & Work-Related",
"Leisure &\nRecreation" = "🎯 Leisure & Recreation",
"Sleep &\nPersonal Care" = "😴 Sleep & Personal Care",
"Household\nActivities" = "🏠 Household Activities"
)
) +
scale_color_manual(values = ring_colors, guide = "none") +
coord_equal(clip = "off") +
facet_wrap(~group) +
theme_void() +
theme(
strip.text = element_text(size = 13, face = "bold",
color = "#3B2F2F", margin = margin(b = 10)),
plot.title = element_text(size = 15, face = "bold",
color = "#956C3B", hjust = 0.5,
margin = margin(b = 5)),
plot.subtitle = element_text(size = 10, color = "#8B7355",
hjust = 0.5, margin = margin(b = 15)),
plot.caption = element_text(size = 8, color = "#8B7355", hjust = 0.5),
plot.margin = margin(10, 10, 10, 10),
legend.position = "bottom",
legend.text = element_text(size = 9, color = "#3B2F2F"),
legend.key.size = unit(0.5, "cm"),
legend.spacing.x = unit(0.8, "cm")
) +
guides(fill = guide_legend(nrow = 1)) +
labs(
title = "Activity Rings: Career-Changing Graduate Student",
subtitle = "Each ring shows hours spent as a % of the target value (capped at 100%)",
caption = "Source: ATUS 2003–2024 | Peer Average = career-changer group average; My Week = personal time estimates\nFor \"at most\" targets (working, leisure, household), a full ring means the limit was reached."
)This analysis, of over two decades of ATUS microdata, reveals several interesting patterns in how Americans allocate their time. Household activity time is highest among Americans aged 65+ (2.5 hours per day). College undergraduates average 2.7 hours per day on educational activities, below the 5.0-hour study target we set for the app. Parental attention per child falls from 0.7 hours per day in one-child households to 0.4 in households with three or more, consistent with attention being a finite resource that is divided as families grow.
For the time-tracking app, our three target demographics: stay-at-home dads, college undergraduates, and career-changing graduate students, each present distinct time-use patterns and distinct gaps between average and target behavior. These gaps represent the core opportunity for “gamification”: nudging users toward healthier time allocation by showing them how they compare to peers and to evidence-based targets.
Future analyses could incorporate longitudinal tracking to understand how individual time-use evolves over life stages, or integrate well-being survey data to identify which time allocations are most strongly associated with reported happiness and life satisfaction.
Claude (Anthropic) was used in this mini-project to assist with R code generation, debugging, and structuring the analytical pipeline. Specifically, AI assistance was used to help write and troubleshoot the data loading functions, join logic between ATUS files, and ggplot2 visualization code. The original written narrative, including the introduction, interpretive paragraphs, and conclusion, was written by the student without AI assistance, in accordance with course policy.
For the October 2026 republication, Claude was also used to audit the code, to implement the corrections described in the republication note, and to revise the narrative sentences affected by the corrected results.
This work ©2026 by Git User: raulsolanavarro was initially prepared as a Mini-Project for STA 9750 at Baruch College. More details about this course can be found at the course site and instructions for this assignment can be found at MP #02
Social & Recreational Time: Married vs. Unmarried
Marriage is often associated with a shift in social priorities. The table below examines whether married and unmarried Americans actually differ in how much time they dedicate to social and recreational activities. Here, that means everything the ATUS groups under “Socializing, Relaxing, and Leisure” (which includes watching TV) and under “Sports, Exercise, and Recreation”.
View R Code
Unmarried Americans spend more time on social and recreational activities than married Americans (5.35 vs. 4.66 hours per day). One possible reason is that married Americans are more likely to have children at home, which takes up a large share of their time. It may also reflect that unmarried people rely more heavily on social activities as a primary source of connection and entertainment. This table does not test either explanation, and the two groups also differ in age and employment.