From dca9afbe6c97ea9d92888602a6861741ce03ef03 Mon Sep 17 00:00:00 2001 From: Mason Garrison <6001608+smasongarrison@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:19:16 -0400 Subject: [PATCH 01/12] Update vignette to use synthetic cousin data and improve output Replaces previous wide data with a synthetic cousin dataset for demonstration, ensuring reproducibility and clarity. Adds set.seed for reproducibility, updates data selection and transformation steps, and improves table outputs by specifying digits and captions. Also clarifies regression model equations, improves code chunk organization, and enhances explanations for between-family and discordant-kinship regression sections. --- vignettes/full_data_workflow.Rmd | 116 ++++++++++++++++++------------- 1 file changed, 67 insertions(+), 49 deletions(-) diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index 1235615..d3eb58e 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -77,7 +77,7 @@ df_wide <- data.frame( weight_s1 = c(70, 60, 80, 75, 65), weight_s2 = c(68, 62, 78, 74, 66) ) -df_wide %>% +df_wide %>% slice(1:5) %>% knitr::kable() ``` @@ -100,7 +100,7 @@ df_long <- df_wide %>% names_sep = "_s" # original suffix delimiter in column names ) -df_long %>% +df_long %>% slice(1:10) %>% knitr::kable() ``` @@ -122,7 +122,7 @@ df_long2wide <- df_long %>% names_sep = "_s" # ensures id_s1, id_s2,etc ) -df_long2wide %>% +df_long2wide %>% slice(1:5) %>% knitr::kable() ``` @@ -176,7 +176,6 @@ ggpedigree(potter, config = list( focal_fill_component = "additive", focal_fill_method = "steps",# # focal_fill_method = "viridis_c", - focal_fill_use_log = FALSE, focal_fill_n_breaks = 10, sex_color_include = TRUE, @@ -198,7 +197,7 @@ df_ped <- potter %>% mutate (x_var = round(rnorm(nrow(.), mean = 0, sd = 1), digits = 2)) df_ped %>% slice(1:5) %>% - knitr::kable() + knitr::kable(digits = 2) ``` As you can see, each individual is represented in a separate row, with columns for their unique identifier, mother's identifier, father's identifier, and other relevant variables. @@ -236,7 +235,7 @@ df_links <- com2links( df_links %>% slice(1:5) %>% - knitr::kable() + knitr::kable(digits = 3) ``` As you can see, the `df_links` data frame contains pairs of individuals (ID1 and ID2) along with their additive genetic relatedness (`addRel`) and shared environment status (`cnuRel`). These data are in wide format, with each row representing a unique pair of individuals. @@ -255,31 +254,31 @@ This table shows the number of kinship pairs for each combination of genetic rel For demonstration, we'll focus on first cousins raised in separate homes from our pedigree data. This subset allows us to illustrate the process clearly. ```{r} -syn_df <- discord::kinsim( +set.seed(2024) +df_cousin <- df_links %>% + filter(addRel == .125) %>% # only cousins %>% + filter(cnuRel == 0) # only kin raised in separate homes + +df_synthetic <- discord::kinsim( mu_all = c(2, 2), cov_a = .4, - cov_e = .4, - c_vector = df_links$cnuRel, - r_vector = df_links$addRel - #c(df_cousin$cnuRel,df_cousin$cnuRel,df_cousin$cnuRel), - # r_vector = c(df_cousin$addRel,df_cousin$addRel,df_cousin$addRel) + cov_e = .3, + c_vector = c(df_cousin$cnuRel,df_cousin$cnuRel,df_cousin$cnuRel), + r_vector = c(df_cousin$addRel,df_cousin$addRel,df_cousin$addRel) ) %>% - select(id,r, weight_1 =y1_1, - weight_2= y1_2, - height_1 = y2_1, - height_2 = y2_2)# %>% + select(pid = id, + r, + weight_s1 = y1_1, + weight_s2 = y1_2, + height_s1 = y2_1, + height_s2 = y2_2)%>% + mutate( + age_s1 = round(rnorm(nrow(.), mean = 30, sd = 5), digits = 0), + age_s2 = age_s1 - sample(1:5, nrow(.), replace = TRUE) + ) - - - -#%>% -# cbind(df_links,.) - - - -df_cousin <- df_links %>% - filter(addRel == .125) %>% # only cousins %>% - filter(cnuRel == 0) # only kin raised in separate homes +df_synthetic %>% glimpse() %>% slice(1:5) %>% + knitr::kable(digits = 2) ``` @@ -298,8 +297,8 @@ Below we default to the long path for reproducibility. Replace with your chosen ```{r} # CHOOSE ONE based on your path # source_wide <- df_wide -source_wide <- df_long2wide -# source_wide <- pairs_wide # if you followed the pedigree path +#source_wide <- df_long2wide +source_wide <- df_synthetic # if you followed the pedigree path ``` Now call `discord_data()` specifying the outcome and predictor present for both siblings. Here we use `Y` as the outcome and `X` as the predictor, with your `_S1` / `_S2` suffix convention. @@ -315,8 +314,8 @@ df_discord_weight <- discord::discord_data( ) df_discord_weight %>% - slice(1:5) %>% - knitr::kable() + glimpse() %>% slice(1:5) %>% + knitr::kable(digits = 2, caption = "Transformed data ready for discordant-kinship regression") ``` @@ -329,7 +328,7 @@ df_discord_weight %>% select(id, weight_1, weight_2, weight_mean, weight_diff, height_1, height_2, height_mean, height_diff) %>% - slice(1:8) %>% + slice(1:5) %>% knitr::kable(digits = 2) ``` @@ -355,7 +354,7 @@ For our baseline analysis, we select one sibling from each pair. In the original ```{r select-for-ols} -df_for_ols <- df_wide %>% +df_for_ols <- df_synthetic %>% dplyr::select( id = pid, weight = weight_s1, @@ -374,26 +373,33 @@ df_discord_age <- discord::discord_data( ) df_discord_age %>% - slice(1:5) %>% select(id, age_1, age_2, age_mean, age_diff, weight_1, height_1 ) %>% - knitr::kable(caption = "One sibling per pair for OLS regression, selecting by age") + select(id, age_1, age_2, + age_mean, age_diff, weight_1, height_1 ) %>% + slice(1:5) %>% + knitr::kable(caption = "One sibling per pair for OLS regression, selecting by age", digits = 2) df_for_ols %>% + select(id, age, weight, height) %>% slice(1:5) %>% - knitr::kable(caption = "One sibling per pair for OLS regression using orginal wide data") + knitr::kable(caption = "One sibling per pair for OLS regression using orginal wide data", digits = 2) ``` As you can see, `df_for_ols` and `df_discord_age` both resulted in the same individuals being selected for analysis. The `df_discord_age` dataset was created by ordering siblings based on age, ensuring that `_1` is always the older sibling. The `df_for_ols` dataset directly selects the older sibling from the original wide data. + By selecting one sibling per pair, we're analyzing the same number of observations as we have kinship pairs. This represents the standard individual-level approach used in most research: -```{r ols-regression} -ols_model <- lm(weight ~ height + age, data = df_for_ols) +$$ Y = \\beta_0 + \\beta_1 X1 + \\beta_2 X2 + \\epsilon $$ -stargazer::stargazer(ols_model, type = "html" - , digits = 3, single.row = TRUE, title = "Standard OLS Regression Results") +```{r ols-regression} +ols_model <- lm(weight ~ height + age, data = df_for_ols) +``` +```{r ols-output, message=FALSE, warning=FALSE, results='asis'} +stargazer::stargazer(ols_model, type = "html", + digits = 3, single.row = TRUE, title = "Standard OLS Regression Results") ``` This standard regression shows associations between our variables while controlling only for measured covariates. @@ -403,18 +409,22 @@ This standard regression shows associations between our variables while controll Next, we run a between-family regression using the discordant data. This model regresses the outcome mean on predictor means, capturing between-family variation: -```{r between-family} +$$ Y_{mean} = \\beta_0 + \\beta_1 X1_{mean} + \\beta_2 X2_{mean} + \\epsilon $$ + +```{r between-family} between_model <- lm( weight_mean ~ height_mean + age_mean, data = df_discord_weight ) - -stargazer::stargazer(between_model, type = "text" - , digits = 3, single.row = TRUE, title = "Between-Family Regression Results") +``` +```{r between-output, results='asis', message=FALSE, warning=FALSE} +stargazer::stargazer(between_model, type = "html", + digits = 3, single.row = TRUE, title = "Between-Family Regression Results") ``` + This between-family regression captures how differences in average height and age across families relate to average weight. However, it does not account for within-family differences, which is where discordant-kinship regression comes in. It is important to note that the between-family regression results are not directly comparable to the standard OLS regression results. The between-family model uses mean scores, which represent family-level averages, while the OLS model uses individual-level data. Therefore, the coefficients from these two models may differ in magnitude and interpretation. But, they both provide useful information about the relationships among the variables. @@ -425,7 +435,7 @@ Now we return to our prepared data to run discordant-kinship regression. We can The discordant model regresses the outcome difference on outcome mean, predictor means, and predictor differences: - +$$ Y_{diff} = \\beta_0 + \\beta_1 Y_{mean} + \\beta_2 X1_{mean} + \\beta_3 X1_{diff} + \\beta_4 X2_{mean} + \\beta_5 X2_{diff} + \\epsilon $$ ```{r discord-manual} discord_model_manual <- lm( @@ -435,17 +445,20 @@ discord_model_manual <- lm( tidy(discord_model_manual, conf.int = TRUE) %>% knitr::kable(digits = 3, caption = "Discordant Regression (Manual)") - - -stargazer::stargazer(between_model,discord_model_manual, type = "text" +``` +```{r discord-manual-output, results='asis', message=FALSE, warning=FALSE} +stargazer::stargazer(between_model,discord_model_manual, type = "html" , digits = 3, single.row = TRUE, title = "Between-Family and Discordant Regression Results") ``` +Note that we are using the df_discord_weight dataset created earlier with `discord_data()`. This dataset is ordered such that within a pair, the sibling with the higher weight is always `_1`. This ordering ensures that `weight_diff` is always non-negative, which is a key feature of discordant-kinship regression. + ### Using the Helper Function Alternatively, we can use the `discord_regression()` function, which simplifies the process: +By default, `discord_regression()` orders siblings by the outcome variable, creates the necessary derived variables, and fits the discordant regression model in one step. This function also allows for easy inclusion of demographic covariates if needed. See the function documentation for more details on these options, as well as a vignette focused on demographic controls. ```{r discord-function} discord_model <- discord_regression( @@ -466,7 +479,12 @@ glance(discord_model) %>% select(r.squared, adj.r.squared, sigma, p.value, nobs) %>% knitr::kable(digits = 3) ``` - +```{r discord-compare, results='asis'} +stargazer::stargazer(discord_model, discord_model_manual, + type = "html", + digits = 3, single.row = TRUE, + title = "Discordant Regression Results Comparison") +``` ```{r} sessionInfo() ``` From 558337f5c1a343ccabc0870a0dfbdb3321b1da2f Mon Sep 17 00:00:00 2001 From: Mason Garrison <6001608+smasongarrison@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:19:16 -0400 Subject: [PATCH 02/12] Update vignette to use synthetic cousin data and improve output Replaces previous wide data with a synthetic cousin dataset for demonstration, ensuring reproducibility and clarity. Adds set.seed for reproducibility, updates data selection and transformation steps, and improves table outputs by specifying digits and captions. Also clarifies regression model equations, improves code chunk organization, and enhances explanations for between-family and discordant-kinship regression sections. --- vignettes/full_data_workflow.Rmd | 120 ++++++++++++++++++------------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index 1235615..97f4238 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -77,7 +77,7 @@ df_wide <- data.frame( weight_s1 = c(70, 60, 80, 75, 65), weight_s2 = c(68, 62, 78, 74, 66) ) -df_wide %>% +df_wide %>% slice(1:5) %>% knitr::kable() ``` @@ -100,7 +100,7 @@ df_long <- df_wide %>% names_sep = "_s" # original suffix delimiter in column names ) -df_long %>% +df_long %>% slice(1:10) %>% knitr::kable() ``` @@ -122,7 +122,7 @@ df_long2wide <- df_long %>% names_sep = "_s" # ensures id_s1, id_s2,etc ) -df_long2wide %>% +df_long2wide %>% slice(1:5) %>% knitr::kable() ``` @@ -176,7 +176,6 @@ ggpedigree(potter, config = list( focal_fill_component = "additive", focal_fill_method = "steps",# # focal_fill_method = "viridis_c", - focal_fill_use_log = FALSE, focal_fill_n_breaks = 10, sex_color_include = TRUE, @@ -198,7 +197,7 @@ df_ped <- potter %>% mutate (x_var = round(rnorm(nrow(.), mean = 0, sd = 1), digits = 2)) df_ped %>% slice(1:5) %>% - knitr::kable() + knitr::kable(digits = 2) ``` As you can see, each individual is represented in a separate row, with columns for their unique identifier, mother's identifier, father's identifier, and other relevant variables. @@ -236,7 +235,7 @@ df_links <- com2links( df_links %>% slice(1:5) %>% - knitr::kable() + knitr::kable(digits = 3) ``` As you can see, the `df_links` data frame contains pairs of individuals (ID1 and ID2) along with their additive genetic relatedness (`addRel`) and shared environment status (`cnuRel`). These data are in wide format, with each row representing a unique pair of individuals. @@ -255,32 +254,33 @@ This table shows the number of kinship pairs for each combination of genetic rel For demonstration, we'll focus on first cousins raised in separate homes from our pedigree data. This subset allows us to illustrate the process clearly. ```{r} -syn_df <- discord::kinsim( - mu_all = c(2, 2), - cov_a = .4, - cov_e = .4, - c_vector = df_links$cnuRel, - r_vector = df_links$addRel - #c(df_cousin$cnuRel,df_cousin$cnuRel,df_cousin$cnuRel), - # r_vector = c(df_cousin$addRel,df_cousin$addRel,df_cousin$addRel) -) %>% - select(id,r, weight_1 =y1_1, - weight_2= y1_2, - height_1 = y2_1, - height_2 = y2_2)# %>% - - - - -#%>% -# cbind(df_links,.) - - - +set.seed(2024) df_cousin <- df_links %>% filter(addRel == .125) %>% # only cousins %>% filter(cnuRel == 0) # only kin raised in separate homes +df_synthetic <- discord::kinsim( + mu_all = c(1, 1), + cov_a = .5, + cov_c = .1, + cov_e = .3, + c_vector = c(df_cousin$cnuRel,df_cousin$cnuRel,df_cousin$cnuRel), + r_vector = c(df_cousin$addRel,df_cousin$addRel,df_cousin$addRel) +) %>% + select(pid = id, + r, + weight_s1 = y1_1, + weight_s2 = y1_2, + height_s1 = y2_1, + height_s2 = y2_2)%>% + mutate( + age_s1 = round(rnorm(nrow(.), mean = 30, sd = 5), digits = 0), + age_s2 = age_s1 - sample(1:5, nrow(.), replace = TRUE) + ) + +df_synthetic %>% glimpse() %>% slice(1:5) %>% + knitr::kable(digits = 2) + ``` @@ -298,8 +298,8 @@ Below we default to the long path for reproducibility. Replace with your chosen ```{r} # CHOOSE ONE based on your path # source_wide <- df_wide -source_wide <- df_long2wide -# source_wide <- pairs_wide # if you followed the pedigree path +#source_wide <- df_long2wide +source_wide <- df_synthetic # if you followed the pedigree path ``` Now call `discord_data()` specifying the outcome and predictor present for both siblings. Here we use `Y` as the outcome and `X` as the predictor, with your `_S1` / `_S2` suffix convention. @@ -315,8 +315,8 @@ df_discord_weight <- discord::discord_data( ) df_discord_weight %>% - slice(1:5) %>% - knitr::kable() + glimpse() %>% slice(1:5) %>% + knitr::kable(digits = 2, caption = "Transformed data ready for discordant-kinship regression") ``` @@ -329,7 +329,7 @@ df_discord_weight %>% select(id, weight_1, weight_2, weight_mean, weight_diff, height_1, height_2, height_mean, height_diff) %>% - slice(1:8) %>% + slice(1:5) %>% knitr::kable(digits = 2) ``` @@ -355,7 +355,7 @@ For our baseline analysis, we select one sibling from each pair. In the original ```{r select-for-ols} -df_for_ols <- df_wide %>% +df_for_ols <- df_synthetic %>% dplyr::select( id = pid, weight = weight_s1, @@ -374,26 +374,33 @@ df_discord_age <- discord::discord_data( ) df_discord_age %>% - slice(1:5) %>% select(id, age_1, age_2, age_mean, age_diff, weight_1, height_1 ) %>% - knitr::kable(caption = "One sibling per pair for OLS regression, selecting by age") + select(id, age_1, age_2, + age_mean, age_diff, weight_1, height_1 ) %>% + slice(1:5) %>% + knitr::kable(caption = "One sibling per pair for OLS regression, selecting by age", digits = 2) df_for_ols %>% + select(id, age, weight, height) %>% slice(1:5) %>% - knitr::kable(caption = "One sibling per pair for OLS regression using orginal wide data") + knitr::kable(caption = "One sibling per pair for OLS regression using orginal wide data", digits = 2) ``` As you can see, `df_for_ols` and `df_discord_age` both resulted in the same individuals being selected for analysis. The `df_discord_age` dataset was created by ordering siblings based on age, ensuring that `_1` is always the older sibling. The `df_for_ols` dataset directly selects the older sibling from the original wide data. + By selecting one sibling per pair, we're analyzing the same number of observations as we have kinship pairs. This represents the standard individual-level approach used in most research: -```{r ols-regression} -ols_model <- lm(weight ~ height + age, data = df_for_ols) +$$ Y = \\beta_0 + \\beta_1 X1 + \\beta_2 X2 + \\epsilon $$ -stargazer::stargazer(ols_model, type = "html" - , digits = 3, single.row = TRUE, title = "Standard OLS Regression Results") +```{r ols-regression} +ols_model <- lm(weight ~ height + age, data = df_for_ols) +``` +```{r ols-output, message=FALSE, warning=FALSE, results='asis'} +stargazer::stargazer(ols_model, type = "html", + digits = 3, single.row = TRUE, title = "Standard OLS Regression Results") ``` This standard regression shows associations between our variables while controlling only for measured covariates. @@ -403,18 +410,22 @@ This standard regression shows associations between our variables while controll Next, we run a between-family regression using the discordant data. This model regresses the outcome mean on predictor means, capturing between-family variation: -```{r between-family} +$$ Y_{mean} = \\beta_0 + \\beta_1 X1_{mean} + \\beta_2 X2_{mean} + \\epsilon $$ + +```{r between-family} between_model <- lm( weight_mean ~ height_mean + age_mean, data = df_discord_weight ) - -stargazer::stargazer(between_model, type = "text" - , digits = 3, single.row = TRUE, title = "Between-Family Regression Results") +``` +```{r between-output, results='asis', message=FALSE, warning=FALSE} +stargazer::stargazer(between_model, type = "html", + digits = 3, single.row = TRUE, title = "Between-Family Regression Results") ``` + This between-family regression captures how differences in average height and age across families relate to average weight. However, it does not account for within-family differences, which is where discordant-kinship regression comes in. It is important to note that the between-family regression results are not directly comparable to the standard OLS regression results. The between-family model uses mean scores, which represent family-level averages, while the OLS model uses individual-level data. Therefore, the coefficients from these two models may differ in magnitude and interpretation. But, they both provide useful information about the relationships among the variables. @@ -425,7 +436,7 @@ Now we return to our prepared data to run discordant-kinship regression. We can The discordant model regresses the outcome difference on outcome mean, predictor means, and predictor differences: - +$$ Y_{diff} = \\beta_0 + \\beta_1 Y_{mean} + \\beta_2 X1_{mean} + \\beta_3 X1_{diff} + \\beta_4 X2_{mean} + \\beta_5 X2_{diff} + \\epsilon $$ ```{r discord-manual} discord_model_manual <- lm( @@ -435,17 +446,20 @@ discord_model_manual <- lm( tidy(discord_model_manual, conf.int = TRUE) %>% knitr::kable(digits = 3, caption = "Discordant Regression (Manual)") - - -stargazer::stargazer(between_model,discord_model_manual, type = "text" +``` +```{r discord-manual-output, results='asis', message=FALSE, warning=FALSE} +stargazer::stargazer(between_model,discord_model_manual, type = "html" , digits = 3, single.row = TRUE, title = "Between-Family and Discordant Regression Results") ``` +Note that we are using the df_discord_weight dataset created earlier with `discord_data()`. This dataset is ordered such that within a pair, the sibling with the higher weight is always `_1`. This ordering ensures that `weight_diff` is always non-negative, which is a key feature of discordant-kinship regression. + ### Using the Helper Function Alternatively, we can use the `discord_regression()` function, which simplifies the process: +By default, `discord_regression()` orders siblings by the outcome variable, creates the necessary derived variables, and fits the discordant regression model in one step. This function also allows for easy inclusion of demographic covariates if needed. See the function documentation for more details on these options, as well as a vignette focused on demographic controls. ```{r discord-function} discord_model <- discord_regression( @@ -466,7 +480,13 @@ glance(discord_model) %>% select(r.squared, adj.r.squared, sigma, p.value, nobs) %>% knitr::kable(digits = 3) ``` - +```{r discord-compare, results='asis'} +stargazer::stargazer(discord_model, + discord_model_manual, + type = "html", + digits = 3, single.row = TRUE, + title = "Discordant Regression Results Comparison") +``` ```{r} sessionInfo() ``` From d29c58542b6370151c4b1c5f52996545b976c791 Mon Sep 17 00:00:00 2001 From: Mason Garrison <6001608+smasongarrison@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:28:10 -0400 Subject: [PATCH 03/12] Update full_data_workflow.Rmd --- vignettes/full_data_workflow.Rmd | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index 97f4238..d39fbf5 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -448,8 +448,8 @@ tidy(discord_model_manual, conf.int = TRUE) %>% knitr::kable(digits = 3, caption = "Discordant Regression (Manual)") ``` ```{r discord-manual-output, results='asis', message=FALSE, warning=FALSE} -stargazer::stargazer(between_model,discord_model_manual, type = "html" - , digits = 3, single.row = TRUE, title = "Between-Family and Discordant Regression Results") +stargazer::stargazer(between_model,discord_model_manual, type = "html", + digits = 3, single.row = TRUE, title = "Between-Family and Discordant Regression Results") ``` @@ -482,10 +482,8 @@ glance(discord_model) %>% ``` ```{r discord-compare, results='asis'} stargazer::stargazer(discord_model, - discord_model_manual, - type = "html", - digits = 3, single.row = TRUE, - title = "Discordant Regression Results Comparison") + discord_model_manual, type = "html", + digits = 3, single.row = TRUE, title = "Discordant Regression Results Comparison") ``` ```{r} sessionInfo() From feb88d5d4b7d42a1988a84490e5a7480b7c846a3 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Sun, 5 Oct 2025 17:34:35 -0400 Subject: [PATCH 04/12] # This is a combination of 2 commits. # This is the 1st commit message: Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Mason Garrison # This is the commit message #2: Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Mason Garrison --- vignettes/full_data_workflow.Rmd | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index d39fbf5..9e68f18 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -278,7 +278,8 @@ df_synthetic <- discord::kinsim( age_s2 = age_s1 - sample(1:5, nrow(.), replace = TRUE) ) -df_synthetic %>% glimpse() %>% slice(1:5) %>% +df_synthetic %>% + slice(1:5) %>% knitr::kable(digits = 2) ``` @@ -315,7 +316,7 @@ df_discord_weight <- discord::discord_data( ) df_discord_weight %>% - glimpse() %>% slice(1:5) %>% + slice(1:5) %>% knitr::kable(digits = 2, caption = "Transformed data ready for discordant-kinship regression") ``` From eb3b2e14e3c5375d2b0ef0f06e06ac347061794b Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Sun, 5 Oct 2025 17:35:55 -0400 Subject: [PATCH 05/12] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Mason Garrison Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Mason Garrison Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Mason Garrison --- vignettes/full_data_workflow.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index 9e68f18..c47a871 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -383,7 +383,7 @@ df_discord_age %>% df_for_ols %>% select(id, age, weight, height) %>% slice(1:5) %>% - knitr::kable(caption = "One sibling per pair for OLS regression using orginal wide data", digits = 2) + knitr::kable(caption = "One sibling per pair for OLS regression using original wide data", digits = 2) ``` From 8a19c6b7fd66dcf8a76a0e8aa0d8bb5cdb4db415 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 6 Oct 2025 10:50:34 -0400 Subject: [PATCH 06/12] Revise regression vignette for clarity and detail Expanded the introduction with a clearer overview and context for the discordant-kinship regression example.Added a session info section for reproducibility. --- vignettes/regression.Rmd | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/vignettes/regression.Rmd b/vignettes/regression.Rmd index 127fcf3..ecdedc5 100644 --- a/vignettes/regression.Rmd +++ b/vignettes/regression.Rmd @@ -23,10 +23,10 @@ options(rmarkdown.html_vignette.check_title = FALSE) # Introduction -This vignette presents a simple example of a discordant-kinship regression using the `discord` package. +This vignette shows how to run a **discordant-kinship regression** with the `{discord}` package on a small, reproducible example. The example uses data on flu vaccination and socioeconomic status (SES) from the National Longitudinal Survey of Youth 1979 (NLSY79). The goal of this analysis is to examine whether SES at age 40 predicts the number of flu vaccinations received between 2006 and 2016, while controlling for genetic and shared environmental factors by leveraging a discordant-kinship design. -The analysis draws from work presented by Trattner et al (2020) [@jonathantrattner2020], which was initially motivated by reports of health disparities among ethnic minority groups during the COVID-19 pandemic [@hooper2020]. -The data come from the 1979 National Longitudinal Survey of Youth (NLSY79), a nationally-representative household probability sample jointly sponsored by the U.S. Bureau of Labor Statistics and the Department of Defense. Participants were surveyed annually from 1979 until 1994 at which point surveys occurred biennially. The data are publicly available at and include responses from a biennial flu vaccine survey administered between 2006 and 2016. +## Data Description +We build on Trattner et al. (2020) [@jonathantrattner2020], originally motivated by reports of health disparities among ethnic minority groups during the COVID-19 pandemic [@hooper2020]. The data come from the 1979 National Longitudinal Survey of Youth (NLSY79), a nationally-representative household probability sample jointly sponsored by the U.S. Bureau of Labor Statistics and the Department of Defense. Participants were surveyed annually from 1979 until 1994 at which point surveys occurred biennially. The data are publicly available at and include responses from a biennial flu vaccine survey administered between 2006 and 2016. The original analysis examined whether socioeconomic status (SES) at age 40 predicted flu vaccination rates, using a discordant kinship design. For this vignette, the data were downloaded using the [NLS Investigator](https://www.nlsinfo.org/investigator/pages/login) and are available [here](https://github.com/R-Computing-Lab/discord/blob/main/data-raw/flu_shot.dat). SES at age 40 values can be found [here](https://github.com/R-Computing-Lab/discord/blob/main/data-raw/nlsy-ses.csv). For clarity and to emphasize the functionality of {discord}, the data has been pre-processed using [this script](https://github.com/R-Computing-Lab/discord/blob/main/data-raw/preprocess-discord-flu.R). This analysis is enabled by recent work that inferred genetic relatedness for approximately 95% of kin pairs in the NLSY79 cohort [@rodgers2016]. These kinship links are provided in the [{NlsyLinks}](https://nlsy-links.github.io/NlsyLinks/index.html) R package [@beasley2016], and can be easily utilized with the {discord} package. @@ -35,7 +35,9 @@ The original analysis examined whether socioeconomic status (SES) at age 40 pred ## Data Cleaning -For this example, we will load the following packages: +### Prepare person-level data + +To perform a discordant-kinship regression, we first need to preprocess the data. For this example, we will load the following packages: ```{r discord-setup, message = FALSE} # For easy data manipulation @@ -91,7 +93,10 @@ link_vars <- c( ) ``` -We now link the subjects by the specified variables using `CreatePairLinksSingleEntered()`, from the {NlsyLinks} package. + +### Create kinship-linked data + +We now link the subjects by the specified variables using `CreatePairLinksSingleEntered()`, from the {NlsyLinks} package. We filter full siblings (RFull == .5) and use RelationshipPath == "Gen1Housemates"; see {NlsyLinks} docs for other kin types. ```{r create-linked-data} # Specify NLSY database and kin relatedness @@ -105,7 +110,7 @@ df_link <- CreatePairLinksSingleEntered( ) ``` -We have saved this data frame as `df_link`. A random subset of this data is shown below::[^discord-2] +We have saved this data frame as `df_link`. A random subset of this data is shown below:[^discord-2] ```{r preview-linked-dat, echo = FALSE, eval = knitr::is_html_output(),error=FALSE} df_link %>% @@ -277,10 +282,9 @@ The goal of performing a discordant-kinship regression is to see whether there i - # Conclusion -In its current implementation, the {discord} package encourages best practices for performing discordant-kinship regressions. For example, the main function has the default expectation that sex and race indicators will be supplied. These measures are both important covariates **when testing for causality between familial background and psychological characteristics.** +In its current implementation, the {discord} package encourages best practices for performing discordant-kinship regressions. For example, the main function has the default expectation that demographic covariates (sex and race) will be supplied. These measures are both important covariates **when testing for causality between familial background and psychological characteristics.** This, and other design choices, are crucial to facilitating transparent and reproducible results. Software ever-evolves, however, and to further support reproducible research we plan to provide improved documentation and allow for easier inspection of the underlying model implementation and results. @@ -288,4 +292,11 @@ This, and other design choices, are crucial to facilitating transparent and repr We acknowledge contributions from Cermet Ream, Joe Rodgers, and support from Lucy D'Agostino McGowan on this project. + +# Session Info + +```{r session-info, echo = FALSE} +sessioninfo::session_info() +``` + # References From 15019790c46e1b4bb90b701d5cb8d399b5ca9cd0 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 6 Oct 2025 11:13:00 -0400 Subject: [PATCH 07/12] using readme to make the repo-relationships clear --- DESCRIPTION | 3 +- NEWS.md | 2 ++ README.Rmd | 35 ++++++++++++++++++++---- man/check_sibling_order_ram_optimized.Rd | 2 +- man/discord_between_model.Rd | 4 +-- man/discord_data.Rd | 6 ++-- man/discord_data_fast.Rd | 4 +-- man/discord_data_ram_optimized.Rd | 4 +-- man/discord_regression.Rd | 4 +-- man/discord_regression_legacy.Rd | 2 +- vignettes/full_data_workflow.Rmd | 4 ++- 11 files changed, 50 insertions(+), 20 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ba385e8..ad1f3ae 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -39,6 +39,7 @@ Suggests: stargazer, snakecase, testthat, - tidyverse + tidyverse, + sessioninfo VignetteBuilder: knitr diff --git a/NEWS.md b/NEWS.md index 587ff18..730eada 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # discord 1.3 * Adding new full data tutorial in response to reviewers +* Adding links to external repositories for reproducible examples from publications # discord 1.2.4.1 * Added a new vignette demonstrating ways to visualize discordant kinship data using the `ggplot2` package. @@ -7,6 +8,7 @@ * Vectorizing `discord_data()` to improve performance. * Adding tests to ensure comparability between optimized and non-optimized versions of `discord_data()`. * Adding `discord_between_model()` to get the between-family model +* Adding `discord_within_model()` to get the within-family model * Added unique filter for `discord_data()` to ensure that the data is not duplicated. * Added tests for categorical variables in `discord_data()`. * Added hotfix to BGmisc vignette. diff --git a/README.Rmd b/README.Rmd index 67e16c2..29b0f32 100644 --- a/README.Rmd +++ b/README.Rmd @@ -27,11 +27,36 @@ knitr::opts_chunk$set( -The goal of discord is to provide functions for discordant kinship -modeling and other sibling-based quasi-experimental designs. It has -highly customizable, efficient code for generating genetically-informed -simulations and provides user-friendly functions to perform -discordant-kinship regressions. +`discord` is an R package that provides functions for discordant kinship +modeling and other sibling-based quasi-experimental designs. It includes +functions for data preparation, regression analysis, and simulation of +genetically-informed data. The package is designed to facilitate the +implementation of discordant sibling designs in research, allowing for +the control of shared familial confounding factors. + +Visit the [discord website](https://r-computing-lab.github.io/discord/) for +more information and detailed documentation. Below is a brief overview of the package, its features, and vignettes to get you started. + +## Features +- **Data Preparation**: Functions to prepare and structure data for discordant sibling analysis, including handling of kinship pairs and demographic variables. +- **Regression Analysis**: Tools to perform discordant regression analyses, allowing for the examination of within-family effects while controlling for shared familial confounders. +- **Simulation**: Functions to simulate genetically-informed data, enabling researchers to test and validate their models. + +## Vignettes + +The package includes several vignettes to help users understand and utilize its features effectively. + +## External Reproducible Examples + +Beyond the vignettes, you can find additional examples that fully reproduce analyses from our other publications (Garrison et al 2025, etc). These examples can be accessed via the following links: + +- National Longitudinal Survey of Youth (NLSY) dataset + - [Frontiers](https://github.com/R-Computing-Lab/Sims-et-al-2024): Reproduces exploring the relationship between depression and delinquency from Sims et al 2024. + - [AMPPS](https://github.com/R-Computing-Lab/target-causalclaims): Reproduces analyses from Garrison et al 2025, using `targets` for workflow management. + +- China Family Panel Studies (CFPS) dataset + - [AMPPS](https://github.com/R-Computing-Lab/discord_AMPSS_CFPS): Reproduces analyses from the China Family Panel Studies (CFPS) dataset, focusing on the association between adolescent depression and math achievement. + ## Installation diff --git a/man/check_sibling_order_ram_optimized.Rd b/man/check_sibling_order_ram_optimized.Rd index 518ec55..68268d9 100644 --- a/man/check_sibling_order_ram_optimized.Rd +++ b/man/check_sibling_order_ram_optimized.Rd @@ -13,7 +13,7 @@ check_sibling_order_ram_optimized(data, outcome, pair_identifiers, row) interest.} \item{pair_identifiers}{A character vector of length two that contains the -variable identifier for each kinship pair} +variable identifier for each kinship pair. Default is c("_s1","_s2").} \item{row}{The row number of the data frame} } diff --git a/man/discord_between_model.Rd b/man/discord_between_model.Rd index 2f39cd9..e63de7a 100644 --- a/man/discord_between_model.Rd +++ b/man/discord_between_model.Rd @@ -25,7 +25,7 @@ discord_between_model( interest.} \item{predictors}{A character vector containing the column names for -predicting the outcome.} +predicting the outcome. Can be NULL if no predictors are desired.} \item{demographics}{Indicator variable for if the data has the sex and race demographics. If both are present (default, and recommended), value should @@ -39,7 +39,7 @@ corresponding to unique kinship pair identifiers.} \item{race}{A character string for the race column name.} \item{pair_identifiers}{A character vector of length two that contains the -variable identifier for each kinship pair} +variable identifier for each kinship pair. Default is c("_s1","_s2").} \item{data_processed}{Logical operator if data are already preprocessed by discord_data , default is FALSE} diff --git a/man/discord_data.Rd b/man/discord_data.Rd index 0bf0e67..f7b30b5 100644 --- a/man/discord_data.Rd +++ b/man/discord_data.Rd @@ -11,7 +11,7 @@ discord_data( id = NULL, sex = "sex", race = "race", - pair_identifiers, + pair_identifiers = c("_s1", "_s2"), demographics = "both", coding_method = "none", fast = TRUE, @@ -25,7 +25,7 @@ discord_data( interest.} \item{predictors}{A character vector containing the column names for -predicting the outcome.} +predicting the outcome. Can be NULL if no predictors are desired.} \item{id}{Default's to NULL. If supplied, must specify the column name corresponding to unique kinship pair identifiers.} @@ -35,7 +35,7 @@ corresponding to unique kinship pair identifiers.} \item{race}{A character string for the race column name.} \item{pair_identifiers}{A character vector of length two that contains the -variable identifier for each kinship pair} +variable identifier for each kinship pair. Default is c("_s1","_s2").} \item{demographics}{Indicator variable for if the data has the sex and race demographics. If both are present (default, and recommended), value should diff --git a/man/discord_data_fast.Rd b/man/discord_data_fast.Rd index 4bc1d86..1c7e8fe 100644 --- a/man/discord_data_fast.Rd +++ b/man/discord_data_fast.Rd @@ -23,7 +23,7 @@ discord_data_fast( interest.} \item{predictors}{A character vector containing the column names for -predicting the outcome.} +predicting the outcome. Can be NULL if no predictors are desired.} \item{id}{Default's to NULL. If supplied, must specify the column name corresponding to unique kinship pair identifiers.} @@ -33,7 +33,7 @@ corresponding to unique kinship pair identifiers.} \item{race}{A character string for the race column name.} \item{pair_identifiers}{A character vector of length two that contains the -variable identifier for each kinship pair} +variable identifier for each kinship pair. Default is c("_s1","_s2").} \item{demographics}{Indicator variable for if the data has the sex and race demographics. If both are present (default, and recommended), value should diff --git a/man/discord_data_ram_optimized.Rd b/man/discord_data_ram_optimized.Rd index 278f5f3..42aeb29 100644 --- a/man/discord_data_ram_optimized.Rd +++ b/man/discord_data_ram_optimized.Rd @@ -23,7 +23,7 @@ discord_data_ram_optimized( interest.} \item{predictors}{A character vector containing the column names for -predicting the outcome.} +predicting the outcome. Can be NULL if no predictors are desired.} \item{id}{Default's to NULL. If supplied, must specify the column name corresponding to unique kinship pair identifiers.} @@ -33,7 +33,7 @@ corresponding to unique kinship pair identifiers.} \item{race}{A character string for the race column name.} \item{pair_identifiers}{A character vector of length two that contains the -variable identifier for each kinship pair} +variable identifier for each kinship pair. Default is c("_s1","_s2").} \item{demographics}{Indicator variable for if the data has the sex and race demographics. If both are present (default, and recommended), value should diff --git a/man/discord_regression.Rd b/man/discord_regression.Rd index 08aea2f..9bfba52 100644 --- a/man/discord_regression.Rd +++ b/man/discord_regression.Rd @@ -40,7 +40,7 @@ discord_within_model( interest.} \item{predictors}{A character vector containing the column names for -predicting the outcome.} +predicting the outcome. Can be NULL if no predictors are desired.} \item{demographics}{Indicator variable for if the data has the sex and race demographics. If both are present (default, and recommended), value should @@ -54,7 +54,7 @@ corresponding to unique kinship pair identifiers.} \item{race}{A character string for the race column name.} \item{pair_identifiers}{A character vector of length two that contains the -variable identifier for each kinship pair} +variable identifier for each kinship pair. Default is c("_s1","_s2").} \item{data_processed}{Logical operator if data are already preprocessed by discord_data , default is FALSE} diff --git a/man/discord_regression_legacy.Rd b/man/discord_regression_legacy.Rd index 3d909f3..7aa980a 100644 --- a/man/discord_regression_legacy.Rd +++ b/man/discord_regression_legacy.Rd @@ -18,7 +18,7 @@ discord_regression_legacy( interest.} \item{predictors}{A character vector containing the column names for -predicting the outcome.} +predicting the outcome. Can be NULL if no predictors are desired.} \item{more_args}{Optional string to add additional inputs to formula} diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index c47a871..2539f26 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -486,6 +486,8 @@ stargazer::stargazer(discord_model, discord_model_manual, type = "html", digits = 3, single.row = TRUE, title = "Discordant Regression Results Comparison") ``` + +# Session Info ```{r} -sessionInfo() +sessioninfo::sessionInfo() ``` From d87dee99700c2a7e4d8f1aa55951d4a6159efd90 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 6 Oct 2025 13:06:55 -0400 Subject: [PATCH 08/12] Update documentation and citation details Expanded the README with more detailed package description, features, and external reproducible example links. Updated citation information to reflect the current repository URL and removed the year and DOI. Fixed a vignette code example to use sessioninfo::session_info() instead of sessioninfo::sessionInfo(). --- README.Rmd | 3 +- README.md | 68 ++++++++++++++++++++++++++------ vignettes/full_data_workflow.Rmd | 2 +- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/README.Rmd b/README.Rmd index 29b0f32..e63f109 100644 --- a/README.Rmd +++ b/README.Rmd @@ -51,11 +51,12 @@ The package includes several vignettes to help users understand and utilize its Beyond the vignettes, you can find additional examples that fully reproduce analyses from our other publications (Garrison et al 2025, etc). These examples can be accessed via the following links: - National Longitudinal Survey of Youth (NLSY) dataset + - [Intelligence](https://github.com/R-Computing-Lab/Project_AFI_Intelligence): Reproduces Garrison, S. M., & Rodgers, J. L. (2016). Casting doubt on the causal link between intelligence and age at first intercourse: A cross-generational sibling comparison design using the NLSY. Intelligence, 59, 139-156. - [Frontiers](https://github.com/R-Computing-Lab/Sims-et-al-2024): Reproduces exploring the relationship between depression and delinquency from Sims et al 2024. - [AMPPS](https://github.com/R-Computing-Lab/target-causalclaims): Reproduces analyses from Garrison et al 2025, using `targets` for workflow management. - China Family Panel Studies (CFPS) dataset - - [AMPPS](https://github.com/R-Computing-Lab/discord_AMPSS_CFPS): Reproduces analyses from the China Family Panel Studies (CFPS) dataset, focusing on the association between adolescent depression and math achievement. + - [AMPPS](https://github.com/R-Computing-Lab/discord_CFPS): Reproduces analyses from the China Family Panel Studies (CFPS) dataset, focusing on the association between adolescent depression and math achievement. ## Installation diff --git a/README.md b/README.md index 0b2b9d6..ab7454a 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,56 @@ coverage](https://codecov.io/gh/R-Computing-Lab/discord/graph/badge.svg)](https: -The goal of discord is to provide functions for discordant kinship -modeling and other sibling-based quasi-experimental designs. It has -highly customizable, efficient code for generating genetically-informed -simulations and provides user-friendly functions to perform -discordant-kinship regressions. +`discord` is an R package that provides functions for discordant kinship +modeling and other sibling-based quasi-experimental designs. It includes +functions for data preparation, regression analysis, and simulation of +genetically-informed data. The package is designed to facilitate the +implementation of discordant sibling designs in research, allowing for +the control of shared familial confounding factors. + +Visit the [discord website](https://r-computing-lab.github.io/discord/) +for more information and detailed documentation. Below is a brief +overview of the package, its features, and vignettes to get you started. + +## Features + +- **Data Preparation**: Functions to prepare and structure data for + discordant sibling analysis, including handling of kinship pairs and + demographic variables. +- **Regression Analysis**: Tools to perform discordant regression + analyses, allowing for the examination of within-family effects while + controlling for shared familial confounders. +- **Simulation**: Functions to simulate genetically-informed data, + enabling researchers to test and validate their models. + +## Vignettes + +The package includes several vignettes to help users understand and +utilize its features effectively. + +## External Reproducible Examples + +Beyond the vignettes, you can find additional examples that fully +reproduce analyses from our other publications (Garrison et al 2025, +etc). These examples can be accessed via the following links: + +- National Longitudinal Survey of Youth (NLSY) dataset + - [Intelligence](https://github.com/R-Computing-Lab/Project_AFI_Intelligence): + Reproduces Garrison, S. M., & Rodgers, J. L. (2016). Casting doubt + on the causal link between intelligence and age at first + intercourse: A cross-generational sibling comparison design using + the NLSY. Intelligence, 59, 139-156. + - [Frontiers](https://github.com/R-Computing-Lab/Sims-et-al-2024): + Reproduces exploring the relationship between depression and + delinquency from Sims et al 2024. + - [AMPPS](https://github.com/R-Computing-Lab/target-causalclaims): + Reproduces analyses from Garrison et al 2025, using `targets` for + workflow management. +- China Family Panel Studies (CFPS) dataset + - [AMPPS](https://github.com/R-Computing-Lab/discord_CFPS): Reproduces + analyses from the China Family Panel Studies (CFPS) dataset, + focusing on the association between adolescent depression and math + achievement. ## Installation @@ -53,22 +98,21 @@ cite the following paper: ``` r citation(package = "discord") +Warning in citation(package = "discord"): could not determine year for +'discord' from package DESCRIPTION file To cite package 'discord' in publications use: - Garrison S, Trattner J, Hwang Y (2025). _discord: Functions for - Discordant Kinship Modeling_. doi:10.32614/CRAN.package.discord - , R package version - 1.2.4.1, . + Garrison S, Trattner J, Hwang Y (????). _discord: Functions for + Discordant Kinship Modeling_. R package version 1.2.4.1, + . A BibTeX entry for LaTeX users is @Manual{, title = {discord: Functions for Discordant Kinship Modeling}, author = {S. Mason Garrison and Jonathan Trattner and Yoo Ri Hwang}, - year = {2025}, note = {R package version 1.2.4.1}, - url = {https://CRAN.R-project.org/package=discord}, - doi = {10.32614/CRAN.package.discord}, + url = {https://github.com/R-Computing-Lab/discord}, } ``` diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index 2539f26..286fee2 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -489,5 +489,5 @@ stargazer::stargazer(discord_model, # Session Info ```{r} -sessioninfo::sessionInfo() +sessioninfo::session_info() ``` From 879270191a40f877e7fe2a5228400605882be4c0 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 6 Oct 2025 13:08:40 -0400 Subject: [PATCH 09/12] stylr --- R/func_discord_data.R | 12 +- R/helpers_regression.R | 2 +- tests/testthat/test-discord_regression.R | 88 +++++----- .../test-discord_regression_results.R | 1 - vignettes/full_data_workflow.Rmd | 158 ++++++++++-------- 5 files changed, 139 insertions(+), 122 deletions(-) diff --git a/R/func_discord_data.R b/R/func_discord_data.R index 774560c..088e8f5 100644 --- a/R/func_discord_data.R +++ b/R/func_discord_data.R @@ -41,14 +41,12 @@ discord_data <- function(data, id = NULL, sex = "sex", race = "race", - pair_identifiers = c("_s1","_s2"), + pair_identifiers = c("_s1", "_s2"), demographics = "both", coding_method = "none", fast = TRUE, ...) { - - - if (fast==TRUE) { + if (fast == TRUE) { unique(discord_data_fast( data = data, outcome = outcome, @@ -226,8 +224,10 @@ discord_data_fast <- function(data, )) { id_orginal <- id id <- "rowwise_id" - orderedOnOutcome <- cbind(orderedOnOutcome, id_orginal = data[id_orginal], - rowwise_id = 1:nrow(data)) + orderedOnOutcome <- cbind(orderedOnOutcome, + id_orginal = data[id_orginal], + rowwise_id = 1:nrow(data) + ) } #------------------------------------------- diff --git a/R/helpers_regression.R b/R/helpers_regression.R index fc144d6..451d6de 100644 --- a/R/helpers_regression.R +++ b/R/helpers_regression.R @@ -185,7 +185,7 @@ make_mean_diffs_ram_optimized <- function(data, id, sex, race, demographics, recode_demographics <- function(demographics, data, raceS1, raceS2, race, sexS1, sexS2, sex, coding_method, output, fast = FALSE) { # check for whether or not race and sex are defined - if (fast==TRUE) { + if (fast == TRUE) { if (demographics == "race") { output_demographics <- data.frame( race_1 = data[[raceS1]], diff --git a/tests/testthat/test-discord_regression.R b/tests/testthat/test-discord_regression.R index d962b7a..df604b3 100644 --- a/tests/testthat/test-discord_regression.R +++ b/tests/testthat/test-discord_regression.R @@ -15,6 +15,7 @@ default_setup <- function(slice = TRUE) { data(data_flu_ses) link_pairs <- Links79PairExpanded %>% filter(RelationshipPath == "Gen1Housemates" & RFull == 0.5) + df_link <- NlsyLinks::CreatePairLinksSingleEntered( outcomeDataset = data_flu_ses, linksPairDataset = link_pairs, @@ -28,10 +29,11 @@ default_setup <- function(slice = TRUE) { RACE_S2 = ifelse(RACE_S2 == 0, "NONMINORITY", "MINORITY") ) %>% filter(RACE_S1 == RACE_S2) - if (slice==TRUE) {df_link <- df_link %>% - group_by(ExtendedID) %>% - slice_sample() %>% - ungroup() + if (slice == TRUE) { + df_link <- df_link %>% + group_by(ExtendedID) %>% + slice_sample() %>% + ungroup() } return(df_link) } @@ -62,27 +64,27 @@ test_that("discord_data with sex coding returns expected columns and values when df_link <- default_setup(slice = TRUE) cat_sex <- discord_data( - data = df_link, - outcome = "S00_H40", - sex = "SEX", - race = "RACE", - demographics = "sex", - predictors = NULL, + data = df_link, + outcome = "S00_H40", + sex = "SEX", + race = "RACE", + demographics = "sex", + predictors = NULL, pair_identifiers = c("_S1", "_S2"), - coding_method = "both", + coding_method = "both", id = "ExtendedID" ) expect_true(all(cat_sex$SEX_multimatch %in% c("MALE", "FEMALE", "mixed"))) expect_true(all(cat_sex$SEX_binarymatch %in% c(0, 1))) -expect_true(all(names(cat_sex) %in% c("id","S00_H40_1","S00_H40_2","S00_H40_diff","S00_H40_mean","SEX_1" ,"SEX_2" ,"SEX_binarymatch","SEX_multimatch"))) -# no duplicate ids -expect_false(any(duplicated(cat_sex$id))) -# expect one row per pair -expect_equal(length(unique(cat_sex$id)), nrow(df_link)) + expect_true(all(names(cat_sex) %in% c("id", "S00_H40_1", "S00_H40_2", "S00_H40_diff", "S00_H40_mean", "SEX_1", "SEX_2", "SEX_binarymatch", "SEX_multimatch"))) + # no duplicate ids + expect_false(any(duplicated(cat_sex$id))) + # expect one row per pair + expect_equal(length(unique(cat_sex$id)), nrow(df_link)) -# expect that ExtendedID is preserved -expect_true(all(cat_sex$id %in% df_link$ExtendedID)) -expect_false(max(cat_sex$id)==nrow(df_link)) + # expect that ExtendedID is preserved + expect_true(all(cat_sex$id %in% df_link$ExtendedID)) + expect_false(max(cat_sex$id) == nrow(df_link)) cat_sex_model <- discord_regression( @@ -110,25 +112,25 @@ test_that("discord_data with sex coding returns expected columns and values when df_link <- default_setup(slice = FALSE) expect_warning(discord_data( - data = df_link, - outcome = "S00_H40", - sex = "SEX", - race = "RACE", - demographics = "sex", - predictors = NULL, + data = df_link, + outcome = "S00_H40", + sex = "SEX", + race = "RACE", + demographics = "sex", + predictors = NULL, pair_identifiers = c("_S1", "_S2"), - coding_method = "both", + coding_method = "both", id = "ExtendedID" )) - cat_sex <- suppressWarnings(discord_data( - data = df_link, - outcome = "S00_H40", - sex = "SEX", - race = "RACE", - demographics = "sex", - predictors = NULL, + cat_sex <- suppressWarnings(discord_data( + data = df_link, + outcome = "S00_H40", + sex = "SEX", + race = "RACE", + demographics = "sex", + predictors = NULL, pair_identifiers = c("_S1", "_S2"), - coding_method = "both", + coding_method = "both", id = "ExtendedID" )) @@ -137,15 +139,15 @@ test_that("discord_data with sex coding returns expected columns and values when expect_true(all(cat_sex$SEX_multimatch %in% c("MALE", "FEMALE", "mixed"))) expect_true(all(cat_sex$SEX_binarymatch %in% c(0, 1))) -expect_true(all(names(cat_sex) %in% c("id","S00_H40_1","S00_H40_2","S00_H40_diff","S00_H40_mean","SEX_1" ,"SEX_2" ,"SEX_binarymatch","SEX_multimatch"))) -# yes duplicate ids -#expect_true(any(duplicated(cat_sex$id))) -# expect one row per pair -#expect_equal(length(unique(cat_sex$id)), nrow(df_link)) - -# expect that ExtendedID is preserved -#expect_true(all(cat_sex$id %in% df_link$ExtendedID)) -#expect_false(max(cat_sex$id)==nrow(df_link)) + expect_true(all(names(cat_sex) %in% c("id", "S00_H40_1", "S00_H40_2", "S00_H40_diff", "S00_H40_mean", "SEX_1", "SEX_2", "SEX_binarymatch", "SEX_multimatch"))) + # yes duplicate ids + # expect_true(any(duplicated(cat_sex$id))) + # expect one row per pair + # expect_equal(length(unique(cat_sex$id)), nrow(df_link)) + + # expect that ExtendedID is preserved + # expect_true(all(cat_sex$id %in% df_link$ExtendedID)) + # expect_false(max(cat_sex$id)==nrow(df_link)) cat_sex_model <- discord_regression( diff --git a/tests/testthat/test-discord_regression_results.R b/tests/testthat/test-discord_regression_results.R index 54f829b..dffa0d2 100644 --- a/tests/testthat/test-discord_regression_results.R +++ b/tests/testthat/test-discord_regression_results.R @@ -162,4 +162,3 @@ test_that("half siblings nonsignificant is as expected", { expect_gt(object = get_p_value(results_ram), expected = signif_threshold) expect_equal(get_p_value(results_fast), get_p_value(results_ram), tolerance = 0.005) }) - diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index 286fee2..564c801 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -50,7 +50,7 @@ library(broom) # For pipe library(magrittr) # For pedigree data manipulation -library(BGmisc) +library(BGmisc) # For pedigree plotting library(ggpedigree) library(ggplot2) @@ -76,8 +76,9 @@ df_wide <- data.frame( height_s2 = c(170, 162, 178, 172, 168), weight_s1 = c(70, 60, 80, 75, 65), weight_s2 = c(68, 62, 78, 74, 66) -) -df_wide %>% slice(1:5) %>% +) +df_wide %>% + slice(1:5) %>% knitr::kable() ``` @@ -92,15 +93,15 @@ Long format structures data with one row per individual. In other words, each pe We can demonstrate long format by reshaping our wide data from above: ```{r} - df_long <- df_wide %>% tidyr::pivot_longer( - cols = -pid, # keep the dyad identifier intact + cols = -pid, # keep the dyad identifier intact names_to = c(".value", "sibling"), # split base names and the sibling marker names_sep = "_s" # original suffix delimiter in column names ) -df_long %>% slice(1:10) %>% +df_long %>% + slice(1:10) %>% knitr::kable() ``` @@ -118,11 +119,12 @@ To convert long format data for discordant-kinship analysis, use `pivot_wider()` df_long2wide <- df_long %>% tidyr::pivot_wider( names_from = sibling, # the column that indicates the sibling number - values_from = c(id, age, height, weight), # variables to spread into paired columns + values_from = c(id, age, height, weight), # variables to spread into paired columns names_sep = "_s" # ensures id_s1, id_s2,etc ) -df_long2wide %>% slice(1:5) %>% +df_long2wide %>% + slice(1:5) %>% knitr::kable() ``` @@ -139,14 +141,16 @@ For NLSY data specifically, the {NlsyLinks} package provides validated kinship l library(NlsyLinks) data(Links79PairExpanded) -Links79PairExpanded %>% - arrange(ExtendedID) %>% - filter(RelationshipPath == "Gen1Housemates" & RFull == 0.5) %>% - slice_head(n=5) %>% - select(ExtendedID, - SubjectTag_S1, SubjectTag_S2, - RelationshipPath, RFull, IsMz , - EverSharedHouse) %>% +Links79PairExpanded %>% + arrange(ExtendedID) %>% + filter(RelationshipPath == "Gen1Housemates" & RFull == 0.5) %>% + slice_head(n = 5) %>% + select( + ExtendedID, + SubjectTag_S1, SubjectTag_S2, + RelationshipPath, RFull, IsMz, + EverSharedHouse + ) %>% knitr::kable() ``` @@ -165,21 +169,21 @@ data(potter) ggpedigree(potter, config = list( label_method = "geom_text", label_nudge_y = .25, - focal_fill_personID = 7, - focal_fill_include = TRUE, - focal_fill_force_zero = TRUE, - focal_fill_na_value = "grey50", - focal_fill_low_color = "darkred", - focal_fill_high_color = "gold", - focal_fill_mid_color = "orange", - focal_fill_scale_midpoint = .65, - focal_fill_component = "additive", - focal_fill_method = "steps",# - # focal_fill_method = "viridis_c", - focal_fill_use_log = FALSE, - focal_fill_n_breaks = 10, - sex_color_include = TRUE, - focal_fill_legend_title = "Genetic Relatives \nof Harry Potter" + focal_fill_personID = 7, + focal_fill_include = TRUE, + focal_fill_force_zero = TRUE, + focal_fill_na_value = "grey50", + focal_fill_low_color = "darkred", + focal_fill_high_color = "gold", + focal_fill_mid_color = "orange", + focal_fill_scale_midpoint = .65, + focal_fill_component = "additive", + focal_fill_method = "steps", # + # focal_fill_method = "viridis_c", + focal_fill_use_log = FALSE, + focal_fill_n_breaks = 10, + sex_color_include = TRUE, + focal_fill_legend_title = "Genetic Relatives \nof Harry Potter" )) + labs(title = "Potter Pedigree Plot") + theme(legend.position = "right") @@ -192,11 +196,14 @@ The pedigree tree above illustrates the family relationships among individuals i data(potter) df_ped <- potter %>% as.data.frame() %>% - select(personID, sex, famID, momID, dadID, spouseID, - twinID, zygosity) %>% - mutate (x_var = round(rnorm(nrow(.), mean = 0, sd = 1), digits = 2)) + select( + personID, sex, famID, momID, dadID, spouseID, + twinID, zygosity + ) %>% + mutate(x_var = round(rnorm(nrow(.), mean = 0, sd = 1), digits = 2)) -df_ped %>% slice(1:5) %>% +df_ped %>% + slice(1:5) %>% knitr::kable(digits = 2) ``` @@ -245,7 +252,7 @@ Further, we can tally the number of pairs by relatedness and shared environment ```{r} df_links %>% group_by(addRel, cnuRel) %>% - tally() %>% + tally() %>% knitr::kable() ``` @@ -264,24 +271,25 @@ df_synthetic <- discord::kinsim( cov_a = .5, cov_c = .1, cov_e = .3, - c_vector = c(df_cousin$cnuRel,df_cousin$cnuRel,df_cousin$cnuRel), - r_vector = c(df_cousin$addRel,df_cousin$addRel,df_cousin$addRel) + c_vector = c(df_cousin$cnuRel, df_cousin$cnuRel, df_cousin$cnuRel), + r_vector = c(df_cousin$addRel, df_cousin$addRel, df_cousin$addRel) ) %>% - select(pid = id, - r, - weight_s1 = y1_1, - weight_s2 = y1_2, - height_s1 = y2_1, - height_s2 = y2_2)%>% + select( + pid = id, + r, + weight_s1 = y1_1, + weight_s2 = y1_2, + height_s1 = y2_1, + height_s2 = y2_2 + ) %>% mutate( age_s1 = round(rnorm(nrow(.), mean = 30, sd = 5), digits = 0), age_s2 = age_s1 - sample(1:5, nrow(.), replace = TRUE) ) - -df_synthetic %>% + +df_synthetic %>% slice(1:5) %>% knitr::kable(digits = 2) - ``` @@ -299,8 +307,8 @@ Below we default to the long path for reproducibility. Replace with your chosen ```{r} # CHOOSE ONE based on your path # source_wide <- df_wide -#source_wide <- df_long2wide -source_wide <- df_synthetic # if you followed the pedigree path +# source_wide <- df_long2wide +source_wide <- df_synthetic # if you followed the pedigree path ``` Now call `discord_data()` specifying the outcome and predictor present for both siblings. Here we use `Y` as the outcome and `X` as the predictor, with your `_S1` / `_S2` suffix convention. @@ -311,7 +319,7 @@ df_discord_weight <- discord::discord_data( outcome = "weight", predictors = c("height", "age"), demographics = "none", - pair_identifiers = c("_s1","_s2"), + pair_identifiers = c("_s1", "_s2"), id = "pid" # or "famID" if you followed the pedigree path ) @@ -327,9 +335,11 @@ Let's examine what `discord_data()` did to our variables: ```{r examine-transformation} df_discord_weight %>% - select(id, - weight_1, weight_2, weight_mean, weight_diff, - height_1, height_2, height_mean, height_diff) %>% + select( + id, + weight_1, weight_2, weight_mean, weight_diff, + height_1, height_2, height_mean, height_diff + ) %>% slice(1:5) %>% knitr::kable(digits = 2) ``` @@ -355,14 +365,13 @@ For our baseline analysis, we select one sibling from each pair. In the original ```{r select-for-ols} - df_for_ols <- df_synthetic %>% - dplyr::select( + dplyr::select( id = pid, weight = weight_s1, height = height_s1, age = age_s1 - ) + ) df_discord_age <- discord::discord_data( @@ -370,13 +379,15 @@ df_discord_age <- discord::discord_data( outcome = "age", predictors = c("height", "weight"), demographics = "none", - pair_identifiers = c("_s1","_s2"), + pair_identifiers = c("_s1", "_s2"), id = "pid" # or "famID" if you followed the pedigree path ) df_discord_age %>% - select(id, age_1, age_2, - age_mean, age_diff, weight_1, height_1 ) %>% + select( + id, age_1, age_2, + age_mean, age_diff, weight_1, height_1 + ) %>% slice(1:5) %>% knitr::kable(caption = "One sibling per pair for OLS regression, selecting by age", digits = 2) @@ -384,8 +395,6 @@ df_for_ols %>% select(id, age, weight, height) %>% slice(1:5) %>% knitr::kable(caption = "One sibling per pair for OLS regression using original wide data", digits = 2) - - ``` As you can see, `df_for_ols` and `df_discord_age` both resulted in the same individuals being selected for analysis. The `df_discord_age` dataset was created by ordering siblings based on age, ensuring that `_1` is always the older sibling. The `df_for_ols` dataset directly selects the older sibling from the original wide data. @@ -400,8 +409,10 @@ $$ Y = \\beta_0 + \\beta_1 X1 + \\beta_2 X2 + \\epsilon $$ ols_model <- lm(weight ~ height + age, data = df_for_ols) ``` ```{r ols-output, message=FALSE, warning=FALSE, results='asis'} -stargazer::stargazer(ols_model, type = "html", - digits = 3, single.row = TRUE, title = "Standard OLS Regression Results") +stargazer::stargazer(ols_model, + type = "html", + digits = 3, single.row = TRUE, title = "Standard OLS Regression Results" +) ``` This standard regression shows associations between our variables while controlling only for measured covariates. @@ -421,9 +432,10 @@ between_model <- lm( ) ``` ```{r between-output, results='asis', message=FALSE, warning=FALSE} -stargazer::stargazer(between_model, type = "html", - digits = 3, single.row = TRUE, title = "Between-Family Regression Results") - +stargazer::stargazer(between_model, + type = "html", + digits = 3, single.row = TRUE, title = "Between-Family Regression Results" +) ``` @@ -449,8 +461,10 @@ tidy(discord_model_manual, conf.int = TRUE) %>% knitr::kable(digits = 3, caption = "Discordant Regression (Manual)") ``` ```{r discord-manual-output, results='asis', message=FALSE, warning=FALSE} -stargazer::stargazer(between_model,discord_model_manual, type = "html", - digits = 3, single.row = TRUE, title = "Between-Family and Discordant Regression Results") +stargazer::stargazer(between_model, discord_model_manual, + type = "html", + digits = 3, single.row = TRUE, title = "Between-Family and Discordant Regression Results" +) ``` @@ -482,9 +496,11 @@ glance(discord_model) %>% knitr::kable(digits = 3) ``` ```{r discord-compare, results='asis'} -stargazer::stargazer(discord_model, - discord_model_manual, type = "html", - digits = 3, single.row = TRUE, title = "Discordant Regression Results Comparison") +stargazer::stargazer(discord_model, + discord_model_manual, + type = "html", + digits = 3, single.row = TRUE, title = "Discordant Regression Results Comparison" +) ``` # Session Info From 5d0970deab5779e644759181d6ba67c55799e7b4 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 6 Oct 2025 13:09:54 -0400 Subject: [PATCH 10/12] Rename test file for regression estimates Renamed 'test-discord_regression_arguments.R' to 'test-discord_regression_estimates.R' to better reflect the contents of the test file. --- ...regression_arguments.R => test-discord_regression_estimates.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/{test-discord_regression_arguments.R => test-discord_regression_estimates.R} (100%) diff --git a/tests/testthat/test-discord_regression_arguments.R b/tests/testthat/test-discord_regression_estimates.R similarity index 100% rename from tests/testthat/test-discord_regression_arguments.R rename to tests/testthat/test-discord_regression_estimates.R From 0edf6a7470d5a63ba7e19ac8318408794722efb2 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 6 Oct 2025 14:24:20 -0400 Subject: [PATCH 11/12] Fix legacy data handling and add more legacy tests Corrects the handling of outcome and predictor variables in the legacy data preparation function to avoid subsetting errors. Adds additional tests to improve coverage of legacy comparison and id handling, as noted in NEWS.md. Bumps package version to 1.3. Add optional id argument for custom kinship pair identifiers Introduces an optional 'id' argument to kinsim() and kinsim_internal() functions, allowing users to specify unique identifiers for each kinship pair. Updates documentation and tests to reflect and validate the new id handling, ensuring backward compatibility with sequential id assignment when not provided. --- DESCRIPTION | 7 ++++--- NEWS.md | 2 ++ R/func_discord_data.R | 4 ++-- R/func_kinsim.R | 17 ++++++++++++++--- R/helpers_simulation.R | 15 ++++++++++++--- R/legacy.R | 10 ++++++---- README.Rmd | 24 ++++++++++++++++++------ README.md | 15 ++++++++++++--- man/discord-package.Rd | 2 +- man/kinsim.Rd | 3 +++ man/kinsim_internal.Rd | 8 +++++++- tests/testthat/test-kinsim.R | 25 +++++++++++++++++++++++++ tests/testthat/test-new-legacy.R | 18 +++++++++++++++++- vignettes/full_data_workflow.Rmd | 9 +++++---- vignettes/regression.Rmd | 6 +++--- 15 files changed, 131 insertions(+), 34 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ad1f3ae..6b011a9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: discord Type: Package Title: Functions for Discordant Kinship Modeling -Version: 1.2.4.1 +Version: 1.3 Authors@R: c(person("S. Mason", "Garrison", email = "garrissm@wfu.edu", role = c("aut", "cre", "cph"), comment = c(ORCID = "0000-0002-4804-6003")), @@ -11,7 +11,7 @@ Authors@R: c(person("S. Mason", "Garrison", email = "garrissm@wfu.edu", person("Yoo Ri", "Hwang", email = "yrhwang89@gmail.com", role = "aut"), person("Cermet", "Ream", role = c("ctb"))) -Description: Functions for discordant kinship modeling (and other sibling-based quasi-experimental designs). Contains data restructuring functions and functions for generating biometrically informed data for kin pairs. See [Garrison and Rodgers, 2016 ], [Sims, Trattner, and Garrison, 2024 ] for empirical examples, and Garrison and colleagues for theoretical work . +Description: Functions for discordant kinship modeling (and other sibling-based quasi-experimental designs). Contains data restructuring functions and functions for generating biometrically informed data for kin pairs. See [Garrison and Rodgers, 2016 ], [Sims, Trattner, and Garrison, 2024 ] for empirical examples, and [Garrison and colleagues for theoretical work ]. URL: https://github.com/R-Computing-Lab/discord, https://r-computing-lab.github.io/discord/ License: GPL-3 @@ -36,10 +36,11 @@ Suggests: magrittr, rmarkdown, scales, + sessioninfo, stargazer, snakecase, testthat, tidyverse, - sessioninfo + tidyr VignetteBuilder: knitr diff --git a/NEWS.md b/NEWS.md index 730eada..3066582 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,8 @@ # discord 1.3 * Adding new full data tutorial in response to reviewers * Adding links to external repositories for reproducible examples from publications +* Added smarter id handling to `discord_data()` +* Added more tests to legacy comparisons and ids # discord 1.2.4.1 * Added a new vignette demonstrating ways to visualize discordant kinship data using the `ggplot2` package. diff --git a/R/func_discord_data.R b/R/func_discord_data.R index 088e8f5..89bcb7f 100644 --- a/R/func_discord_data.R +++ b/R/func_discord_data.R @@ -222,10 +222,10 @@ discord_data_fast <- function(data, if (!valid_ids(orderedOnOutcome, id = id )) { - id_orginal <- id + id_original <- id id <- "rowwise_id" orderedOnOutcome <- cbind(orderedOnOutcome, - id_orginal = data[id_orginal], + id_original = data[id_original], rowwise_id = 1:nrow(data) ) } diff --git a/R/func_kinsim.R b/R/func_kinsim.R index 82488ad..2442ba2 100644 --- a/R/func_kinsim.R +++ b/R/func_kinsim.R @@ -34,6 +34,7 @@ #' @param cov_a Numeric. Shared variance for additive genetics between variables; default is 0. #' @param cov_c Numeric. Shared variance for shared-environment between variables; default is 0. #' @param cov_e Numeric. Shared variance for non-shared-environment between variables; default is 0. +#' @param id Numeric vector. Optional unique identifiers for each kinship pair; #' @param ... Additional arguments passed to other methods. #' @return A data frame with the following columns: @@ -87,6 +88,7 @@ kinsim <- function( cov_a = 0, # default shared covariance for genetics across variables cov_c = 0, # default shared variance for c across variables cov_e = 0, # default shared variance for e across variables + id = NULL, ...) { # Check if the number of rows in ace_list matches the number of variables mu <- NULL @@ -103,7 +105,10 @@ kinsim <- function( npergroup = npergroup_all, # mu = mu_list[1], # intercept ace = ace_list[1, ], - r_vector = r_vector + r_vector = r_vector, + c_vector = c_vector, + id = id, + ... ) data_v$A1_u <- data_v$A1 data_v$A2_u <- data_v$A2 @@ -123,7 +128,10 @@ kinsim <- function( stop("You have tried to generate data beyond the current limitations of this program. Maximum variables 2.") } if (is.null(r_vector)) { - id <- 1:sum(npergroup_all) + if(is.null(id)){ + id <- 1:sum(npergroup_all) + } + for (i in 1:length(r_all)) { n <- npergroup_all[i] @@ -200,7 +208,10 @@ kinsim <- function( merged.data.frame <- Reduce(function(...) merge(..., all = TRUE), datalist) merged.data.frame$id <- id } else { - id <- seq_along(r_vector) + if(is.null(id)){ + id <- seq_along(r_vector) + } + # Initialize full-length empty matrices n <- length(r_vector) A.r <- matrix(NA_real_, nrow = n, ncol = 4) diff --git a/R/helpers_simulation.R b/R/helpers_simulation.R index f279a97..f480c02 100644 --- a/R/helpers_simulation.R +++ b/R/helpers_simulation.R @@ -38,7 +38,10 @@ #' a = additive genetic, c = shared environment, e = non-shared environment; #' default is c(1, 1, 1). #' @param r_vector Numeric vector. Alternative specification method providing relatedness -#' coefficients for the entire sample; default is NULL. +#' coefficients for the entire sample; default is NULL. If provided, \code{r_vector} overrides \code{r} and \code{npergroup}. +#' @param c_vector Numeric vector. Optional vector of shared environmental correlations. for each kinship pair. If provided, \code{c_vector} overrides \code{c_rel} and \code{npergroup}. The length of \code{c_vector} must match that of \code{r_vector} (if provided), or the total number of pairs implied by \code{r} and \code{npergroup}. Values should be in the range [0, 1]. +#' @param id Numeric vector. Optional unique identifiers for each kinship pair; +#' default is NULL, in which case IDs are assigned sequentially. #' @param ... Additional arguments passed to other methods. #' @keywords internal #' @return A data frame with the following columns: @@ -66,6 +69,7 @@ kinsim_internal <- function( ace = c(1, 1, 1), r_vector = NULL, c_vector = NULL, + id = NULL, ...) { # Calculate standard deviations from variance components sA <- ace[1]^0.5 @@ -85,7 +89,10 @@ kinsim_internal <- function( # Handle standard case with groups of different relatedness if (is.null(r_vector)) { - id <- 1:sum(npergroup) + + if(is.null(id)){ + id <- 1:sum(npergroup) + } # Generate data for each relatedness group @@ -129,7 +136,9 @@ kinsim_internal <- function( merged.data.frame$id <- id } else { # Handle case with custom relatedness vector - id <- 1:length(r_vector) + if(is.null(id)){ + id <- 1:length(r_vector) + } data_vector <- data.frame(id, r_vector) data_vector$A.r1 <- as.numeric(NA) diff --git a/R/legacy.R b/R/legacy.R index dfd9391..741ae35 100644 --- a/R/legacy.R +++ b/R/legacy.R @@ -60,8 +60,8 @@ discord_data_legacy <- function( if (!doubleentered) { outcome2x <- outcome2 - outcome2 <- c(outcome2[, 1], outcome1[, 1]) - outcome1 <- c(outcome1[, 1], outcome2x[, 1]) + outcome2 <- c(outcome2, outcome1) #c(outcome2[, 1], outcome1[, 1]) + outcome1 <- c(outcome1, outcome2x) # c(outcome1[, 1], outcome2x[, 1]) if (scale & is.numeric(outcome1)) { outcome1 <- scale(outcome1) @@ -78,8 +78,10 @@ discord_data_legacy <- function( for (i in 1:length(predictors)) { predictor1x <- predictor1 <- subset(df, select = paste0(predictors[i], sep, "1"))[, 1] predictor2 <- subset(df, select = paste0(predictors[i], sep, "2"))[, 1] - predictor1 <- c(predictor1[, 1], predictor2[, 1]) - predictor2 <- c(predictor2[, 1], predictor1x[, 1]) + predictor1 <- c(predictor1, predictor2) + #c(predictor1[, 1], predictor2[, 1]) + predictor2 <- c(predictor2, predictor1x) + #c(predictor2[, 1], predictor1x[, 1]) if (scale & is.numeric(predictor1)) { predictor1 <- scale(predictor1) predictor2 <- scale(predictor2) diff --git a/README.Rmd b/README.Rmd index e63f109..e391383 100644 --- a/README.Rmd +++ b/README.Rmd @@ -44,17 +44,29 @@ more information and detailed documentation. Below is a brief overview of the pa ## Vignettes -The package includes several vignettes to help users understand and utilize its features effectively. +The package includes several vignettes to help users understand and utilize its features effectively. These vignettes can be accessed [online](https://r-computing-lab.github.io/discord/articles/) or by selecting them from the RStudio "Vignettes" tab after installing the package. + +The following vignettes are available: +- [Power Analysis with discord](https://r-computing-lab.github.io/discord/articles/Power.html) +- [Regression Analysis with discord](https://r-computing-lab.github.io/discord/articles/regression.html) +- [Data Preparation with discord](https://r-computing-lab.github.io/discord/articles/data_preparation.html) +- [Handling Categorical Predictors](https://r-computing-lab.github.io/discord/articles/categorical_predictors.html) +- [Full Data Workflow](https://r-computing-lab.github.io/discord/articles/full_data_workflow.html) +- [Creating Plots](https://r-computing-lab.github.io/discord/articles/plots.html) +- [Simulating Genetically-Informed Data](https://r-computing-lab.github.io/discord/articles/links.html) + + ## External Reproducible Examples Beyond the vignettes, you can find additional examples that fully reproduce analyses from our other publications (Garrison et al 2025, etc). These examples can be accessed via the following links: - National Longitudinal Survey of Youth (NLSY) dataset - - [Intelligence](https://github.com/R-Computing-Lab/Project_AFI_Intelligence): Reproduces Garrison, S. M., & Rodgers, J. L. (2016). Casting doubt on the causal link between intelligence and age at first intercourse: A cross-generational sibling comparison design using the NLSY. Intelligence, 59, 139-156. - - [Frontiers](https://github.com/R-Computing-Lab/Sims-et-al-2024): Reproduces exploring the relationship between depression and delinquency from Sims et al 2024. - - [AMPPS](https://github.com/R-Computing-Lab/target-causalclaims): Reproduces analyses from Garrison et al 2025, using `targets` for workflow management. - + - [Intelligence](https://github.com/R-Computing-Lab/Project_AFI_Intelligence): Reproduces Garrison, S. M., & Rodgers, J. L. (2016). Casting doubt on the causal link between intelligence and age at first intercourse: A cross-generational sibling comparison design using the NLSY. Intelligence, 59, 139-156. https://doi.org/10.1016/j.intell.2016.08.008 + - [Frontiers](https://github.com/R-Computing-Lab/Sims-et-al-2024): Reproduces Sims, E. E., Trattner, J. D., & Garrison, S. M. (2024). Exploring the relationship between depression and delinquency: a sibling comparison design using the NLSY. Frontiers in psychology, 15, 1430978. https://doi.org/10.3389/fpsyg.2024.1430978 + + - [AMPPS](https://github.com/R-Computing-Lab/target-causalclaims): Reproduces analyses from Garrison et al 2025, using `targets` for workflow management. Garrison, S. M., Trattner, J. D., Lyu, X., Prillaman, H. R., McKinzie, L., Thompson, S. H. E., & Rodgers, J. L. (2025). Sibling Models Can Test Causal Claims without Experiments: Applications for Psychology. https://doi.org/10.1101/2025.08.25.25334395 + - China Family Panel Studies (CFPS) dataset - [AMPPS](https://github.com/R-Computing-Lab/discord_CFPS): Reproduces analyses from the China Family Panel Studies (CFPS) dataset, focusing on the association between adolescent depression and math achievement. @@ -79,7 +91,7 @@ devtools::install_github('R-Computing-Lab/discord') If you use `discord` in your research or wish to refer to it, please cite the following paper: -```{r eval=TRUE, comment=NA} +```{r eval=TRUE, comment=NA,warning=FALSE, message=FALSE} citation(package = "discord") ``` diff --git a/README.md b/README.md index ab7454a..8cf414b 100644 --- a/README.md +++ b/README.md @@ -61,12 +61,21 @@ etc). These examples can be accessed via the following links: on the causal link between intelligence and age at first intercourse: A cross-generational sibling comparison design using the NLSY. Intelligence, 59, 139-156. + + - [Frontiers](https://github.com/R-Computing-Lab/Sims-et-al-2024): - Reproduces exploring the relationship between depression and - delinquency from Sims et al 2024. + Reproduces Sims, E. E., Trattner, J. D., & Garrison, S. M. (2024). + Exploring the relationship between depression and delinquency: a + sibling comparison design using the NLSY. Frontiers in psychology, + 15, 1430978. + - [AMPPS](https://github.com/R-Computing-Lab/target-causalclaims): Reproduces analyses from Garrison et al 2025, using `targets` for - workflow management. + workflow management. Garrison, S. M., Trattner, J. D., Lyu, X., + Prillaman, H. R., McKinzie, L., Thompson, S. H. E., & Rodgers, J. L. + (2025). Sibling Models Can Test Causal Claims without Experiments: + Applications for Psychology. + - China Family Panel Studies (CFPS) dataset - [AMPPS](https://github.com/R-Computing-Lab/discord_CFPS): Reproduces analyses from the China Family Panel Studies (CFPS) dataset, diff --git a/man/discord-package.Rd b/man/discord-package.Rd index 3238c57..f307300 100644 --- a/man/discord-package.Rd +++ b/man/discord-package.Rd @@ -8,7 +8,7 @@ \description{ \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} -Functions for discordant kinship modeling (and other sibling-based quasi-experimental designs). Contains data restructuring functions and functions for generating biometrically informed data for kin pairs. See [Garrison and Rodgers, 2016 \doi{10.1016/j.intell.2016.08.008}], [Sims, Trattner, and Garrison, 2024 \doi{10.3389/fpsyg.2024.1430978}] for empirical examples, and Garrison and colleagues for theoretical work . +Functions for discordant kinship modeling (and other sibling-based quasi-experimental designs). Contains data restructuring functions and functions for generating biometrically informed data for kin pairs. See [Garrison and Rodgers, 2016 \doi{10.1016/j.intell.2016.08.008}], [Sims, Trattner, and Garrison, 2024 \doi{10.3389/fpsyg.2024.1430978}] for empirical examples, and [Garrison and colleagues for theoretical work ]. } \seealso{ Useful links: diff --git a/man/kinsim.Rd b/man/kinsim.Rd index 5d59e89..15e8442 100644 --- a/man/kinsim.Rd +++ b/man/kinsim.Rd @@ -19,6 +19,7 @@ kinsim( cov_a = 0, cov_c = 0, cov_e = 0, + id = NULL, ... ) } @@ -60,6 +61,8 @@ default repeats \code{ace_all} for each variable.} \item{cov_e}{Numeric. Shared variance for non-shared-environment between variables; default is 0.} +\item{id}{Numeric vector. Optional unique identifiers for each kinship pair;} + \item{...}{Additional arguments passed to other methods.} } \value{ diff --git a/man/kinsim_internal.Rd b/man/kinsim_internal.Rd index 6e3b4ba..03ba2ff 100644 --- a/man/kinsim_internal.Rd +++ b/man/kinsim_internal.Rd @@ -13,6 +13,7 @@ kinsim_internal( ace = c(1, 1, 1), r_vector = NULL, c_vector = NULL, + id = NULL, ... ) } @@ -32,7 +33,12 @@ a = additive genetic, c = shared environment, e = non-shared environment; default is c(1, 1, 1).} \item{r_vector}{Numeric vector. Alternative specification method providing relatedness -coefficients for the entire sample; default is NULL.} +coefficients for the entire sample; default is NULL. If provided, \code{r_vector} overrides \code{r} and \code{npergroup}.} + +\item{c_vector}{Numeric vector. Optional vector of shared environmental correlations. for each kinship pair. If provided, \code{c_vector} overrides \code{c_rel} and \code{npergroup}. The length of \code{c_vector} must match that of \code{r_vector} (if provided), or the total number of pairs implied by \code{r} and \code{npergroup}. Values should be in the range [0, 1].} + +\item{id}{Numeric vector. Optional unique identifiers for each kinship pair; +default is NULL, in which case IDs are assigned sequentially.} \item{...}{Additional arguments passed to other methods.} } diff --git a/tests/testthat/test-kinsim.R b/tests/testthat/test-kinsim.R index 537ffb0..5b653a4 100644 --- a/tests/testthat/test-kinsim.R +++ b/tests/testthat/test-kinsim.R @@ -125,13 +125,35 @@ test_that("kinsim returns correct number of rows based on sample sizes", { }) test_that("kinsim works with a single variable", { + set.seed(123) df <- kinsim(variables = 1) expected_cols <- c( "A1_1", "A1_2", "C1_1", "C1_2", "E1_1", "E1_2", "y1_1", "y1_2", "r", "id" ) expect_true(all(expected_cols %in% names(df))) + # expect_equal(ncol(df), length(expected_cols)) + + expect_true(all(df$id == 1:nrow(df))) + + + +}) + +test_that("kinsim works with a single variable and provided ID", { + set.seed(123) + df <- kinsim(variables = 1, id = 1001:2000) + expected_cols <- c( + "A1_1", "A1_2", "C1_1", "C1_2", "E1_1", "E1_2", "y1_1", "y1_2", "r", "id" + ) + expect_true(all(expected_cols %in% names(df))) + # expect_equal(ncol(df), length(expected_cols)) + expect_true(all(df$id == 1001:2000)) + + expect_false(all(df$id == 1:nrow(df))) + }) + test_that("kinsim fails if more than 2 variables are requested", { expect_error(kinsim(variables = 3), "generate data beyond the current limitations") }) @@ -173,4 +195,7 @@ test_that("kinsim handles r_vector and c_vecttor input correctly", { test_that("output has correct ID range", { df <- kinsim(npergroup_all = c(50, 50)) expect_equal(df$id, 1:100) + + df <- kinsim(npergroup_all = c(50, 50), id = 101:200) + expect_equal(df$id, 101:200) }) diff --git a/tests/testthat/test-new-legacy.R b/tests/testthat/test-new-legacy.R index 9b4f367..aab36c4 100644 --- a/tests/testthat/test-new-legacy.R +++ b/tests/testthat/test-new-legacy.R @@ -34,6 +34,16 @@ test_that("monozygotic significant: new & legacy regression code results are equ ) set.seed(18) + + old_results_nde <- discord_data_legacy( + df = mz_signif[,c("id","y1_1","y1_2","y2_1","y2_2")], + outcome = "y1", + predictors = "y2", + id = "id", + sep = "_", + doubleentered = FALSE + ) + old_results <- discord_data_legacy( df = make_double_entered(mz_signif), outcome = "y1", @@ -47,8 +57,14 @@ test_that("monozygotic significant: new & legacy regression code results are equ outcome = "y1", predictors = "y2" ) - + old_results_nde <- discord_regression_legacy( + df = old_results_nde, + outcome = "y1", + predictors = "y2" + ) expect_equal(summarize_results(new_results), summarize_results(old_results)) + expect_equal(summarize_results(new_results), summarize_results(old_results_nde)) + expect_equal(summarize_results(old_results), summarize_results(old_results_nde)) }) test_that("monozygotic significant: new & legacy data prep code results are equal", { diff --git a/vignettes/full_data_workflow.Rmd b/vignettes/full_data_workflow.Rmd index 564c801..be4e2c5 100644 --- a/vignettes/full_data_workflow.Rmd +++ b/vignettes/full_data_workflow.Rmd @@ -39,13 +39,14 @@ First, load the necessary packages: ```{r discord-setup, message = FALSE} # For easy data manipulation library(dplyr) +library(tidyr) + # For kinship linkages library(NlsyLinks) # For discordant-kinship regression library(discord) -# To clean data frame names +# To clean data frames library(janitor) -# tidy up output library(broom) # For pipe library(magrittr) @@ -271,8 +272,8 @@ df_synthetic <- discord::kinsim( cov_a = .5, cov_c = .1, cov_e = .3, - c_vector = c(df_cousin$cnuRel, df_cousin$cnuRel, df_cousin$cnuRel), - r_vector = c(df_cousin$addRel, df_cousin$addRel, df_cousin$addRel) + c_vector = rep(df_cousin$cnuRel, 3), + r_vector = rep(df_cousin$addRel, 3) ) %>% select( pid = id, diff --git a/vignettes/regression.Rmd b/vignettes/regression.Rmd index ecdedc5..8887399 100644 --- a/vignettes/regression.Rmd +++ b/vignettes/regression.Rmd @@ -1,5 +1,5 @@ --- -title: "Regression demonstration with Flu Vaccination and SES data" +title: "NLSY: Regression demonstration with Flu Vaccination and SES data" author: Jonathan Trattner output: rmarkdown::html_vignette bibliography: references.bib @@ -22,10 +22,10 @@ options(rmarkdown.html_vignette.check_title = FALSE) # Introduction - -This vignette shows how to run a **discordant-kinship regression** with the `{discord}` package on a small, reproducible example. The example uses data on flu vaccination and socioeconomic status (SES) from the National Longitudinal Survey of Youth 1979 (NLSY79). The goal of this analysis is to examine whether SES at age 40 predicts the number of flu vaccinations received between 2006 and 2016, while controlling for genetic and shared environmental factors by leveraging a discordant-kinship design. +This vignette shows how to run a **discordant-kinship regression** with the `{discord}` package and the `{NLSYLinks}` package. The example uses data on flu vaccination and socioeconomic status (SES) from the National Longitudinal Survey of Youth 1979 (NLSY79). The goal of this analysis is to examine whether SES at age 40 predicts the number of flu vaccinations received between 2006 and 2016, while controlling for genetic and shared environmental factors by leveraging a discordant-kinship design. ## Data Description + We build on Trattner et al. (2020) [@jonathantrattner2020], originally motivated by reports of health disparities among ethnic minority groups during the COVID-19 pandemic [@hooper2020]. The data come from the 1979 National Longitudinal Survey of Youth (NLSY79), a nationally-representative household probability sample jointly sponsored by the U.S. Bureau of Labor Statistics and the Department of Defense. Participants were surveyed annually from 1979 until 1994 at which point surveys occurred biennially. The data are publicly available at and include responses from a biennial flu vaccine survey administered between 2006 and 2016. The original analysis examined whether socioeconomic status (SES) at age 40 predicted flu vaccination rates, using a discordant kinship design. For this vignette, the data were downloaded using the [NLS Investigator](https://www.nlsinfo.org/investigator/pages/login) and are available [here](https://github.com/R-Computing-Lab/discord/blob/main/data-raw/flu_shot.dat). SES at age 40 values can be found [here](https://github.com/R-Computing-Lab/discord/blob/main/data-raw/nlsy-ses.csv). For clarity and to emphasize the functionality of {discord}, the data has been pre-processed using [this script](https://github.com/R-Computing-Lab/discord/blob/main/data-raw/preprocess-discord-flu.R). This analysis is enabled by recent work that inferred genetic relatedness for approximately 95% of kin pairs in the NLSY79 cohort [@rodgers2016]. These kinship links are provided in the [{NlsyLinks}](https://nlsy-links.github.io/NlsyLinks/index.html) R package [@beasley2016], and can be easily utilized with the {discord} package. From 2acb65f3168dec619f800498d734e5aaf3f59428 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 6 Oct 2025 16:10:45 -0400 Subject: [PATCH 12/12] Expand and clarify README documentation The README files were updated to provide more detailed descriptions of package features, vignettes, and external reproducible examples. Vignette summaries now include usage guidance and context for each example. Minor formatting improvements and clarifications were made throughout to enhance readability and user understanding. --- README.Rmd | 145 +++++++++++++++++++++++++++++++++++++++++------------ README.md | 60 ++++++++++++++++++++-- 2 files changed, 170 insertions(+), 35 deletions(-) diff --git a/README.Rmd b/README.Rmd index e391383..7c9e289 100644 --- a/README.Rmd +++ b/README.Rmd @@ -16,7 +16,7 @@ knitr::opts_chunk$set( # discord -discord website +discord website [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) [![R package version](https://www.r-pkg.org/badges/version/discord)](https://cran.r-project.org/package=discord) [![Package downloads](https://cranlogs.r-pkg.org/badges/grand-total/discord)](https://cran.r-project.org/package=discord)
@@ -34,42 +34,114 @@ genetically-informed data. The package is designed to facilitate the implementation of discordant sibling designs in research, allowing for the control of shared familial confounding factors. -Visit the [discord website](https://r-computing-lab.github.io/discord/) for -more information and detailed documentation. Below is a brief overview of the package, its features, and vignettes to get you started. +Visit the [discord website](https://r-computing-lab.github.io/discord/) +for more information and detailed documentation. Below is a brief +overview of the package, its features, and vignettes to get you started. ## Features -- **Data Preparation**: Functions to prepare and structure data for discordant sibling analysis, including handling of kinship pairs and demographic variables. -- **Regression Analysis**: Tools to perform discordant regression analyses, allowing for the examination of within-family effects while controlling for shared familial confounders. -- **Simulation**: Functions to simulate genetically-informed data, enabling researchers to test and validate their models. + +- **Data Preparation**: Functions to prepare and structure data for + discordant sibling analysis, including handling of kinship pairs and + demographic variables. +- **Regression Analysis**: Tools to perform discordant regression + analyses, allowing for the examination of within-family effects + while controlling for shared familial confounders. +- **Simulation**: Functions to simulate genetically-informed data, + enabling researchers to test and validate their models. ## Vignettes -The package includes several vignettes to help users understand and utilize its features effectively. These vignettes can be accessed [online](https://r-computing-lab.github.io/discord/articles/) or by selecting them from the RStudio "Vignettes" tab after installing the package. +The package includes several vignettes to help users understand and +utilize its features effectively. These vignettes can be accessed +[online](https://r-computing-lab.github.io/discord/articles/) or by +selecting them from the RStudio "Vignettes" tab after installing the +package. The following vignettes are available: -- [Power Analysis with discord](https://r-computing-lab.github.io/discord/articles/Power.html) -- [Regression Analysis with discord](https://r-computing-lab.github.io/discord/articles/regression.html) -- [Data Preparation with discord](https://r-computing-lab.github.io/discord/articles/data_preparation.html) -- [Handling Categorical Predictors](https://r-computing-lab.github.io/discord/articles/categorical_predictors.html) -- [Full Data Workflow](https://r-computing-lab.github.io/discord/articles/full_data_workflow.html) -- [Creating Plots](https://r-computing-lab.github.io/discord/articles/plots.html) -- [Simulating Genetically-Informed Data](https://r-computing-lab.github.io/discord/articles/links.html) - +- [Power Analysis with discord](https://r-computing-lab.github.io/discord/articles/Power.html) + - Use this vignette when you need to plan sample sizes or evaluate + detectability by running simulation grids that vary effect + sizes, kin types, and Ns using kinsim, then re-fitting + discord_regression under each condition. It reports empirical + power, writes tidy summaries, and includes code to visualize + power curves across conditions to support design decisions. + +- [Regression Analysis with discord](https://r-computing-lab.github.io/discord/articles/regression.html) + - Use this if you want an end-to-end applied example that links + NLSY79 relatives, cleans variables for flu vaccination and SES, + constructs dyads, and then fits within-family models. You will + learn how to specify discord_regression correctly and interpret + coefficients when predictors vary within pairs versus only + between pairs. + +- [Handling Categorical Predictors](https://r-computing-lab.github.io/discord/articles/categorical_predictors.html) + - This vignette formalizes categorical predictors in discord + designs by separating mixed from between-dyad variables and + making the implied contrasts explicit. It implements + binary-match and multi-category match encodings on concrete + examples (e.g., sex, race), fits the corresponding + discord_regression specifications, and contrasts estimates to + show how encoding choices change interpretation. + +- [Full Data Workflow](https://r-computing-lab.github.io/discord/articles/full_data_workflow.html) + - Starting from raw wide and long person-level inputs, this + vignette builds kin links, aligns IDs, and constructs the + discord_data object with sibling-specific columns ready for + modeling. It then fits a conventional between-family regression + alongside a discord model on the same variables, so you can see + where the within-family estimate diverges and adopt the pipeline + as a template. + +- [Creating Plots](https://r-computing-lab.github.io/discord/articles/plots.html) + - This vignette takes fitted discord_regression outputs and + produces publication-ready ggplot figures of effect estimates + and within-family contrasts with minimal transformation of the + model results. It includes complete plotting code paths you can + reuse, from extracting estimates to saving figures that clearly + communicate within-family findings. + +- [No Database? No Problem: Using discord with Simple Family Structures](https://r-computing-lab.github.io/discord/articles/links.html) + - This vignette is particularly useful for situations when you do + not have existing kinship links and need to build link tables + directly from simple family identifiers. It shows how to + construct the links, optionally simulate phenotypes under + specified structures, and fit discord_regression with + alternative specifications for small or bespoke datasets. ## External Reproducible Examples -Beyond the vignettes, you can find additional examples that fully reproduce analyses from our other publications (Garrison et al 2025, etc). These examples can be accessed via the following links: - -- National Longitudinal Survey of Youth (NLSY) dataset - - [Intelligence](https://github.com/R-Computing-Lab/Project_AFI_Intelligence): Reproduces Garrison, S. M., & Rodgers, J. L. (2016). Casting doubt on the causal link between intelligence and age at first intercourse: A cross-generational sibling comparison design using the NLSY. Intelligence, 59, 139-156. https://doi.org/10.1016/j.intell.2016.08.008 - - [Frontiers](https://github.com/R-Computing-Lab/Sims-et-al-2024): Reproduces Sims, E. E., Trattner, J. D., & Garrison, S. M. (2024). Exploring the relationship between depression and delinquency: a sibling comparison design using the NLSY. Frontiers in psychology, 15, 1430978. https://doi.org/10.3389/fpsyg.2024.1430978 - - - [AMPPS](https://github.com/R-Computing-Lab/target-causalclaims): Reproduces analyses from Garrison et al 2025, using `targets` for workflow management. Garrison, S. M., Trattner, J. D., Lyu, X., Prillaman, H. R., McKinzie, L., Thompson, S. H. E., & Rodgers, J. L. (2025). Sibling Models Can Test Causal Claims without Experiments: Applications for Psychology. https://doi.org/10.1101/2025.08.25.25334395 - -- China Family Panel Studies (CFPS) dataset - - [AMPPS](https://github.com/R-Computing-Lab/discord_CFPS): Reproduces analyses from the China Family Panel Studies (CFPS) dataset, focusing on the association between adolescent depression and math achievement. - +Beyond the vignettes, you can find additional examples that fully +reproduce analyses from our other publications (Garrison et al 2025, +etc). These examples can be accessed via the following links: + +- National Longitudinal Survey of Youth (NLSY) dataset + - [Intelligence](https://github.com/R-Computing-Lab/Project_AFI_Intelligence): + Reproduces Garrison, S. M., & Rodgers, J. L. (2016). Casting + doubt on the causal link between intelligence and age at first + intercourse: A cross-generational sibling comparison design + using the NLSY. Intelligence, 59, 139-156. + + + - [Frontiers](https://github.com/R-Computing-Lab/Sims-et-al-2024): + Reproduces Sims, E. E., Trattner, J. D., & Garrison, S. M. + (2024). Exploring the relationship between depression and + delinquency: a sibling comparison design using the NLSY. + Frontiers in psychology, 15, 1430978. + + + - [AMPPS](https://github.com/R-Computing-Lab/target-causalclaims): + Reproduces analyses from Garrison et al 2025, using `targets` + for workflow management. Garrison, S. M., Trattner, J. D., Lyu, + X., Prillaman, H. R., McKinzie, L., Thompson, S. H. E., & + Rodgers, J. L. (2025). Sibling Models Can Test Causal Claims + without Experiments: Applications for Psychology. + +- China Family Panel Studies (CFPS) dataset + - [AMPPS](https://github.com/R-Computing-Lab/discord_CFPS): + Reproduces analyses from the China Family Panel Studies (CFPS) + dataset, focusing on the association between adolescent + depression and math achievement. ## Installation @@ -79,7 +151,9 @@ You can install the official version from CRAN # Install/update discord with the release version from CRAN. install.packages('discord') ``` -You can also install/update discord with the development version of discord from [GitHub](https://github.com/) with: + +You can also install/update discord with the development version of +discord from [GitHub](https://github.com/) with: ``` r # If devtools is not installed, uncomment the line below. @@ -89,15 +163,24 @@ devtools::install_github('R-Computing-Lab/discord') ## Citation -If you use `discord` in your research or wish to refer to it, please cite the following paper: +If you use `discord` in your research or wish to refer to it, please +cite the following paper: ```{r eval=TRUE, comment=NA,warning=FALSE, message=FALSE} citation(package = "discord") ``` - ## Contributing -Contributions to the `discord` project are welcome. For guidelines on how to contribute, please refer to the [Contributing Guidelines](https://github.com/R-Computing-Lab/discord/blob/main/CONTRIBUTING.md). Issues and pull requests should be submitted on the GitHub repository. For support, please use the GitHub issues page. + +Contributions to the `discord` project are welcome. For guidelines on +how to contribute, please refer to the [Contributing +Guidelines](https://github.com/R-Computing-Lab/discord/blob/main/CONTRIBUTING.md). +Issues and pull requests should be submitted on the GitHub repository. +For support, please use the GitHub issues page. ## License -`discord` is licensed under the GNU General Public License v3.0. For more details, see the [LICENSE](https://github.com/R-Computing-Lab/discord/blob/main/LICENSE) file. + +`discord` is licensed under the GNU General Public License v3.0. For +more details, see the +[LICENSE](https://github.com/R-Computing-Lab/discord/blob/main/LICENSE) +file. diff --git a/README.md b/README.md index 8cf414b..b8f8074 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ -discord website +discord website [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) @@ -47,7 +47,61 @@ overview of the package, its features, and vignettes to get you started. ## Vignettes The package includes several vignettes to help users understand and -utilize its features effectively. +utilize its features effectively. These vignettes can be accessed +[online](https://r-computing-lab.github.io/discord/articles/) or by +selecting them from the RStudio “Vignettes” tab after installing the +package. + +The following vignettes are available: + +- [Power Analysis with + discord](https://r-computing-lab.github.io/discord/articles/Power.html) + - Use this vignette when you need to plan sample sizes or evaluate + detectability by running simulation grids that vary effect sizes, + kin types, and Ns using kinsim, then re-fitting discord_regression + under each condition. It reports empirical power, writes tidy + summaries, and includes code to visualize power curves across + conditions to support design decisions. +- [Regression Analysis with + discord](https://r-computing-lab.github.io/discord/articles/regression.html) + - Use this if you want an end-to-end applied example that links NLSY79 + relatives, cleans variables for flu vaccination and SES, constructs + dyads, and then fits within-family models. You will learn how to + specify discord_regression correctly and interpret coefficients when + predictors vary within pairs versus only between pairs. +- [Handling Categorical + Predictors](https://r-computing-lab.github.io/discord/articles/categorical_predictors.html) + - This vignette formalizes categorical predictors in discord designs + by separating mixed from between-dyad variables and making the + implied contrasts explicit. It implements binary-match and + multi-category match encodings on concrete examples (e.g., sex, + race), fits the corresponding discord_regression specifications, and + contrasts estimates to show how encoding choices change + interpretation. +- [Full Data + Workflow](https://r-computing-lab.github.io/discord/articles/full_data_workflow.html) + - Starting from raw wide and long person-level inputs, this vignette + builds kin links, aligns IDs, and constructs the discord_data object + with sibling-specific columns ready for modeling. It then fits a + conventional between-family regression alongside a discord model on + the same variables, so you can see where the within-family estimate + diverges and adopt the pipeline as a template. +- [Creating + Plots](https://r-computing-lab.github.io/discord/articles/plots.html) + - This vignette takes fitted discord_regression outputs and produces + publication-ready ggplot figures of effect estimates and + within-family contrasts with minimal transformation of the model + results. It includes complete plotting code paths you can reuse, + from extracting estimates to saving figures that clearly communicate + within-family findings. +- [No Database? No Problem: Using discord with Simple Family + Structures](https://r-computing-lab.github.io/discord/articles/links.html) + - This vignette is particularly useful for situations when you do not + have existing kinship links and need to build link tables directly + from simple family identifiers. It shows how to construct the links, + optionally simulate phenotypes under specified structures, and fit + discord_regression with alternative specifications for small or + bespoke datasets. ## External Reproducible Examples @@ -107,8 +161,6 @@ cite the following paper: ``` r citation(package = "discord") -Warning in citation(package = "discord"): could not determine year for -'discord' from package DESCRIPTION file To cite package 'discord' in publications use: Garrison S, Trattner J, Hwang Y (????). _discord: Functions for