library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.1 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ ggplot2 3.5.0 ✔ tibble 3.2.1
## ✔ lubridate 1.9.2 ✔ tidyr 1.3.0
## ✔ purrr 1.0.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(janitor)
##
## 载入程辑包:'janitor'
##
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
library(rstudioapi)
library(docstring)
##
## 载入程辑包:'docstring'
##
## The following object is masked from 'package:utils':
##
## ?
library(dbplyr)
##
## 载入程辑包:'dbplyr'
##
## The following objects are masked from 'package:dplyr':
##
## ident, sql
library(purrr)
library(stringr)
library(reshape2)
##
## 载入程辑包:'reshape2'
##
## The following object is masked from 'package:tidyr':
##
## smiths
library(data.table)
##
## 载入程辑包:'data.table'
##
## The following objects are masked from 'package:reshape2':
##
## dcast, melt
##
## The following objects are masked from 'package:lubridate':
##
## hour, isoweek, mday, minute, month, quarter, second, wday, week,
## yday, year
##
## The following objects are masked from 'package:dplyr':
##
## between, first, last
##
## The following object is masked from 'package:purrr':
##
## transpose
library(knitr)
library(kableExtra)
##
## 载入程辑包:'kableExtra'
##
## The following object is masked from 'package:dplyr':
##
## group_rows
For Part 1, the primary challenge was reshaping the dataset from a wide to a long format. The initial instructions lacked sufficient details about the required output format, so I reached out to Kevin for additional information. Based on his input, I selected the following outcomes from the original cleaned_quit_and_co.csv: “y1_tppd” (year 1 tobacco products per day), “y2_tppd”, “y3_tppd”, “y1_CO_recoded”, “y2_CO_recoded”, and “y3_CO_recoded”. I utilized dbplyr for dataset manipulation due to its SQL-like logic. Additionally, I converted the final output variables “tppd” and “CO” to numeric data types in preparation for Part 2.
The next step involved merging the “baseline.csv” with my newly created dataset “dat_split1”. By renaming the “study_id” column in the baseline dataset to “studyid”, I was able to complete this part with a simple merge function.
# 1.2) Set Working Directory to local version (set to data folder)
wdpath <- selectDirectory(caption = "Select Working Directory", label = "Set WD")
setwd(wdpath)
# 1.3) Load Dataset
dat_baseline <- read.csv("baseline.csv", header = TRUE)
dat_cleaned <- read.csv("cleaned_quit_and_co.csv", header = TRUE)
# Check the structure of the cleaned dataset
str(dat_cleaned)
## 'data.frame': 1166 obs. of 90 variables:
## $ studyid : int 8053 7114 9158 8813 7297 8593 8468 7043 8698 8815 ...
## $ TAU : int 1 NA NA 1 1 NA NA NA 1 1 ...
## $ CHW_rand : int 0 1 1 0 0 1 1 1 0 0 ...
## $ Arm : chr "TAU (Treatment as usual)" "CHW only (Community health worker)" "AD + CHW" "TAU (Treatment as usual)" ...
## $ Final_visit : chr "Baseline" "Baseline" "Year 3" "Year 3" ...
## $ PCP.B.NPI : int 730 757 401 489 229 730 652 295 664 759 ...
## $ PCP.B.Name : int 730 757 401 489 229 730 NA 295 664 759 ...
## $ PCP.B.Clinic : int 268 170 26 165 63 268 16 260 190 190 ...
## $ PCP.B.Clinic_detailed : chr "No" "Yes" "No" "No" ...
## $ PCP.B.Detailed : logi FALSE FALSE FALSE FALSE FALSE FALSE ...
## $ PCP.B.Times_detailed : int 0 0 0 0 0 0 0 0 0 0 ...
## $ PCP.Y2.NPI : int NA NA 401 755 229 541 NA 636 664 296 ...
## $ PCP.Y2.Name : int NA NA 401 755 229 541 NA 636 664 296 ...
## $ PCP.Y2.Clinic : int 16 16 26 173 63 77 16 187 190 109 ...
## $ PCP.Y2.Clinic_detailed : chr " " " " "No" "No" ...
## $ PCP.Y2.Detailed : logi FALSE FALSE FALSE FALSE FALSE FALSE ...
## $ PCP.Y2.Times_detailed : int 0 0 0 0 0 0 0 0 0 0 ...
## $ PCP.Y3.NPI : int NA NA 401 755 220 541 NA 636 NA 296 ...
## $ PCP.Y3.Name : int NA NA 401 755 220 541 NA 636 NA 296 ...
## $ PCP.Y3.Clinic : int 16 16 26 173 63 211 16 187 16 109 ...
## $ PCP.Y3.Clinic_detailed : chr " " " " "No" "No" ...
## $ PCP.Y3.Detailed : chr "FALSE" "FALSE" "FALSE" "FALSE" ...
## $ PCP.Y3.Times_detailed : int 0 0 0 0 0 0 0 0 0 0 ...
## $ PCP.Changed_from_Y2_to_Y3: chr "No year 3" "No year 3" "No" "No" ...
## $ PCP.Any_PCP_detailed : chr "FALSE" "FALSE" "FALSE" "FALSE" ...
## $ Date.Baseline : chr "3/29/2017" "11/9/2016" "1/31/2017" "5/11/2017" ...
## $ Date.Y2 : chr " " " " "4/15/2019" "10/2/2018" ...
## $ Date.Y3 : chr " " " " "11/25/2019" "9/18/2019" ...
## $ SCM.Y2.Any_prescribed : chr " " " " "No" "No" ...
## $ SCM.Y3.Any_prescribed : chr " " " " "No" "No" ...
## $ CHW.Consented : chr " " " " " " "Yes" ...
## $ CHW.More_than_1_visit : chr " " " " " " "Yes" ...
## $ CHW.Number_of_visits : int NA NA NA 12 NA NA NA 68 NA NA ...
## $ SSS.CHR.Randomization : chr " " "CHW only (Community health worker)" "AD + CHW" " " ...
## $ CHW.INT.Visits : int NA NA NA 12 NA NA NA 64 NA NA ...
## $ CHW.INT.CBT_visits : int NA NA NA 0 NA NA NA 10 NA NA ...
## $ rx_FINAL : chr " " " " "CHW" " " ...
## $ group_FINAL : chr " " " " "AD" " " ...
## $ engagementqual : chr " " " " "Minimally engaged with CHW" " " ...
## $ Engaged_BayCoveOnly : int NA NA 1 NA NA NA 0 NA NA NA ...
## $ smallclinicrandomizarion : int NA 1 NA NA NA NA NA NA NA NA ...
## $ CBT_GROUPS_1 : int NA NA NA NA NA NA NA NA NA NA ...
## $ CBT_GROUPS_2TO5 : int NA NA NA NA NA NA NA NA NA NA ...
## $ CBT_GROUPS_6TO10 : int NA NA NA NA NA NA NA 1 NA NA ...
## $ CBT_GROUPS_11TO20 : int NA NA NA NA NA NA NA NA NA NA ...
## $ CBT_GROUPS_21PLUS : int NA NA NA NA NA NA NA NA NA NA ...
## $ CBT_GROUPS_2ORMORE : int NA NA NA NA NA NA NA 1 NA NA ...
## $ PCPEVERDETAILED : int NA NA NA NA NA NA NA NA NA NA ...
## $ GE1_CBTVISIT : int NA NA NA 0 NA NA NA 1 NA NA ...
## $ HousingType : chr " " "Unknown" "24 Hour Group Home" "Independent" ...
## $ VF_Active_OCT_2019 : chr " " " " "Active" "Active" ...
## $ GroupSharedLIving : int 0 0 1 0 0 0 1 0 0 1 ...
## $ ANYTAU : int 1 NA NA 1 1 NA NA NA 1 1 ...
## $ y1_date : chr "3/29/17 9:43" "11/9/16 10:49" "1/31/17 9:38" "5/11/17 12:21" ...
## $ y1_tppd : int 4 16 36 18 8 5 40 20 23 16 ...
## $ y1_tobaccotype_notes : chr "Cigarettes - Newports" "Cigarettes" "Cigarettes" "Cigarettes (Pall mall L&M)" ...
## $ y1_cigs : int 1 1 1 1 1 1 1 1 1 1 ...
## $ y1_cigars : int 0 0 0 0 0 0 0 0 0 0 ...
## $ y1_rollies : int 0 0 0 0 0 0 0 0 0 0 ...
## $ y1_ecigsvape : int 0 0 0 0 0 0 0 0 0 0 ...
## $ y1_morethanone : int 1 1 1 1 1 1 1 1 1 1 ...
## $ y1_tobaccotypes : int 1 1 1 1 1 1 1 1 1 1 ...
## $ y1_CO_recoded : int 2 104 20 21 6 17 19 30 47 20 ...
## $ Y1_CO_NOTES : chr " " " " " " " " ...
## $ y1_CO_time : chr "3/29/17 9:49" "1/4/17 12:17" "1/31/17 9:47" "5/11/17 12:31" ...
## $ y1_lastsmoke : chr "3/28/17 3:00" "1/4/17 12:10" "1/31/17 8:44" "5/11/17 11:50" ...
## $ Y2_QUIT : int NA NA 0 0 0 1 0 0 0 0 ...
## $ y2_tppd : num NA NA 27 12 0.5 0 40 6 4 20 ...
## $ y2_CO_recoded : chr NA NA "43" "23" ...
## $ Y2_CO_NOTES : chr " " " " " " " " ...
## $ y2_co_time : chr NA NA "4/15/19 15:55" "10/2/18 9:57" ...
## $ y2_lastsmoke : chr NA NA "4/15/19 15:25" "10/2/18 8:30" ...
## $ y2_date : chr NA NA "1/31/17" "5/11/17" ...
## $ Y3_QUIT : int NA NA 0 0 0 0 NA 0 NA 0 ...
## $ y3_tppd : chr NA NA "30" "20" ...
## $ Y3_CO_NOTES : chr " " " " " " " " ...
## $ y3_CO_recoded : int NA NA 51 38 NA 2 NA 21 NA 45 ...
## $ y3_CO_time : chr NA NA "43794.66667" "9/18/19 15:29" ...
## $ y3_lastsmoke : chr NA NA "11/25/19 16:17" "9/18/19 15:40" ...
## $ y3_lastsmoke_updated : chr NA NA " " " " ...
## $ y3_date : chr NA NA "11/25/19 16:10" "9/18/19 15:29" ...
## $ y3_number_product_types : int NA NA 1 1 1 1 NA 1 NA 1 ...
## $ y3_concurrent : int NA NA 999 999 999 999 NA 999 NA 999 ...
## $ y3_cigs : chr NA NA "30" " " ...
## $ y3_minicigs : chr NA NA " " " " ...
## $ y3_rollies : chr NA NA " " "20" ...
## $ y3_pipe : int NA NA NA NA NA NA NA NA NA NA ...
## $ y3_ecigsvape : int NA NA NA NA NA NA NA NA NA NA ...
## $ y3_other : chr NA NA " " " " ...
## $ filter_. : int 0 0 0 0 0 0 0 0 0 0 ...
## Warning: Expected 2 pieces. Additional pieces discarded in 3498 rows [3499, 3500, 3501,
## 3502, 3503, 3504, 3505, 3506, 3507, 3508, 3509, 3510, 3511, 3512, 3513, 3514,
## 3515, 3516, 3517, 3518, ...].
## Warning: 强制改变过程中产生了NA
## Warning: 强制改变过程中产生了NA
This task presented two main challenges. First, it required splitting the new dataset “dat_merged” into a sample group randomly. I approached this by selecting 40% of the distinct study IDs and including all rows containing these IDs in the original dataset, resulting in the “dat_final” dataset ready for Part 3. Alternatively, one could perform the group split during Part 1 with the wide format and then convert it to the long format. Given that this task was in Part 2, I used the study ID selection strategy.
Second, it was necessary to include both gender and race information in one demographic summary. I converted all race codes to their respective labels as per the codebook, then split “dat_final” into “male” and “female” subsets and summarized each. This provided three types of summary tables: male, female, and overall. I then counted the number of each race within these subsets, creating a demographic table with the help of Copilot (a local AI assistant). Finally, I used the kable function to enhance the table’s presentation.
# 3.1) Summarize statistics
dat_grouped <- dat_merged %>%
select("studyid", "Arm", "time", "age_1", "sex_1", "race_1", "tppd", "CO") %>%
mutate(time = recode(time, "y1" = "Year 1", "y2" = "Year 2", "y3" = "Year 3"))
# 3.2) Split dataset to 2 randomization group
# Randomly select 40% of unique studyid
set.seed(123)
selected_studyid <- dat_grouped %>%
distinct(studyid) %>%
sample_frac(0.4) %>%
pull(studyid)
# Filter the dataset to include all records for the selected studyid
dat_final <- dat_grouped %>%
filter(studyid %in% selected_studyid)
# 3.3) Split dataset by gender
male <- dat_final %>%
filter(sex_1 == 1)
female <- dat_final %>%
filter(sex_1 == 2)
# 3.4) Count race
# Count the number of each race in the overall dataset
race_counts_overall <- table(dat_final$race_1)
# Count the number of each race in the male subset
race_counts_male <- table(male$race_1)
# Count the number of each race in the female subset
race_counts_female <- table(female$race_1)
# 3.5) Create demographic summary
# Manually typed summary table
race_labels <- c("White", "Haitian, Black, or African American", "Asian",
"Hawaiian or other Pacific Islander", "American Indian/Alaska Native",
"Other", "Multi-race")
# Provided counts
race_counts_overall <- c(621, 507, 54, 3, 12, 102, 99)
race_counts_male <- c(405, 369, 45, 0, 6, 72, 72)
race_counts_female <- c(216, 138, 9, 3, 6, 30, 27)
# Create the demographic summary data frame
demographic_summary <- data.frame(
Metric = c("Count", "Age Mean", "Age SD", "TPPD Mean", "TPPD SD", "Expired CO Mean", "Expired CO SD", "NA Count of TPPD", "NA Count of Expired CO",
paste("Race", race_labels, "Count", sep = " - ")),
Male = c(
nrow(male), # Count of males
mean(male$age_1, na.rm = TRUE), # Mean age of males
sd(male$age_1, na.rm = TRUE), # Standard deviation of age of males
mean(male$tppd, na.rm = TRUE), # Mean TPPD for males
sd(male$tppd, na.rm = TRUE), # Standard deviation of TPPD for males
mean(male$CO, na.rm = TRUE), # Mean Expired CO for males
sd(male$CO, na.rm = TRUE), # Standard deviation of Expired CO for males
sum(is.na(male$tppd)), # Number of NA in TPPD for males
sum(is.na(male$CO)), # Number of NA in Expired CO for males
race_counts_male # Race counts for males
),
Female = c(
nrow(female), # Count of females
mean(female$age_1, na.rm = TRUE), # Mean age of females
sd(female$age_1, na.rm = TRUE), # Standard deviation of age of females
mean(female$tppd, na.rm = TRUE), # Mean TPPD for females
sd(female$tppd, na.rm = TRUE), # Standard deviation of TPPD for females
mean(female$CO, na.rm = TRUE), # Mean Expired CO for females
sd(female$CO, na.rm = TRUE), # Standard deviation of Expired CO for females
sum(is.na(female$tppd)), # Number of NA in TPPD for females
sum(is.na(female$CO)), # Number of NA in Expired CO for females
race_counts_female # Race counts for females
),
Overall = c(
nrow(dat_final), # Count of overall dataset
mean(dat_final$age_1, na.rm = TRUE), # Mean age of overall dataset
sd(dat_final$age_1, na.rm = TRUE), # Standard deviation of age of overall dataset
mean(dat_final$tppd, na.rm = TRUE), # Mean TPPD for overall dataset
sd(dat_final$tppd, na.rm = TRUE), # Standard deviation of TPPD for overall dataset
mean(dat_final$CO, na.rm = TRUE), # Mean Expired CO for overall dataset
sd(dat_final$CO, na.rm = TRUE), # Standard deviation of Expired CO for overall dataset
sum(is.na(dat_final$tppd)), # Number of NA in TPPD for overall dataset
sum(is.na(dat_final$CO)), # Number of NA in Expired CO for overall dataset
race_counts_overall # Race counts for overall dataset
)
)
# Print the demographic summary table
kable(demographic_summary[-1, ], format = "html", col.names = c("", "Male", "Female", "Overall")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = F) %>%
column_spec(1, bold = TRUE) %>%
add_header_above(c("Demographic Summary" = 5))
| Male | Female | Overall | ||
|---|---|---|---|---|
| 2 | Age Mean | 47.37461 | 47.69231 | 47.47210 |
| 3 | Age SD | 13.23356 | 13.72386 | 13.38180 |
| 4 | TPPD Mean | 14.48114 | 14.12791 | 14.37318 |
| 5 | TPPD SD | 11.32228 | 10.73410 | 11.14210 |
| 6 | Expired CO Mean | 25.59192 | 22.00000 | 24.49903 |
| 7 | Expired CO SD | 21.74229 | 16.27637 | 20.29568 |
| 8 | NA Count of TPPD | 208.00000 | 94.00000 | 302.00000 |
| 9 | NA Count of Expired CO | 251.00000 | 115.00000 | 366.00000 |
| 10 | Race - White - Count | 405.00000 | 216.00000 | 621.00000 |
| 11 | Race - Haitian, Black, or African American - Count | 369.00000 | 138.00000 | 507.00000 |
| 12 | Race - Asian - Count | 45.00000 | 9.00000 | 54.00000 |
| 13 | Race - Hawaiian or other Pacific Islander - Count | 0.00000 | 3.00000 | 3.00000 |
| 14 | Race - American Indian/Alaska Native - Count | 6.00000 | 6.00000 | 12.00000 |
| 15 | Race - Other - Count | 72.00000 | 30.00000 | 102.00000 |
| 16 | Race - Multi-race - Count | 72.00000 | 27.00000 | 99.00000 |
Part 3 was somewhat challenging. I had to decide which outcome to use for the sole plot submission. Initially, I considered using “tppd” (tobacco products per day) as the outcome. However, after Kevin explained the significance of “CO” (expired CO), I realized it would be a more objective outcome compared to “tppd” (which seemed self-reported).
# 4.1) Plot Expired CO Distribution
plot <- ggplot(dat_final, aes(x = time, y = CO, fill = Arm)) +
geom_boxplot() +
labs(title = "Expired CO Distribution by Experimental Arm and Time",
x = "Time",
y = "Expired CO",
fill = "Experimental Arm")
# Print the plot
print(plot)
## Warning: Removed 366 rows containing non-finite outside the scale range
## (`stat_boxplot()`).
The boxplot showing “Expired CO” values over time for different experimental groups is good for seeing the overall effects of the interventions. However, the “AD + CHW” and “AD only” groups don’t show much change over time. To better understand these small differences, we could use violin plots or other types of plots. These might show more details about the data and help us find important patterns that the boxplot doesn’t show.