From 2dc534d62fe4d7c03bc70c709b6a40de1e6e11a9 Mon Sep 17 00:00:00 2001 From: Reiko Okamoto Date: Wed, 29 Jul 2026 16:25:01 -0400 Subject: [PATCH 1/4] Add main and helper functions to calculate SSI --- R/derive_sii.R | 374 +++++++++++++++++++++++++++++++ tests/testthat/test-derive_sii.R | 32 +++ 2 files changed, 406 insertions(+) create mode 100644 R/derive_sii.R create mode 100644 tests/testthat/test-derive_sii.R diff --git a/R/derive_sii.R b/R/derive_sii.R new file mode 100644 index 0000000..e3706de --- /dev/null +++ b/R/derive_sii.R @@ -0,0 +1,374 @@ +derive_sii <- function(df) { + SII <- df |> + derive_SO() |> + derive_FO() |> + derive_FS() |> + dplyr::mutate( + SII = (.data$SO + .data$FO + .data$FS) / 3 + ) |> + dplyr::select(-dplyr::all_of(c("SO", "FO", "FS"))) + + SII +} + +# STRUCTURAL OBJECTIVE +derive_SO <- function(df) { + SO <- df |> + derive_A() |> + derive_B() |> + derive_C() |> + derive_D() |> + derive_E() |> + dplyr::mutate( + SO = (.data$A + .data$B + .data$C + .data$D + .data$E) / 5 + ) |> + dplyr::select(-dplyr::all_of(c("A", "B", "C", "D", "E"))) +} + +# COMMUNITY PARTICIPATION +derive_A <- function(df) { + expected_cols <- paste0( + "SPA_", + c("OUTS", "CHRCH", "CLUB", "EDUC", "NEIBR", "OTACT", "SPORT", "VOLUN") + ) + validate_cols(df, expected_cols) + + df |> + dplyr::mutate( + dplyr::across( + .cols = expected_cols, + .fns = \(x) { + dplyr::case_when( + x == 1 ~ 0, # at least once a day + x == 2 ~ 2.5, # at least once a week + x == 3 ~ 5, # at least once a month + x == 4 ~ 7.5, # at least once a year + x == 5 ~ 10, # never + .default = NA + ) + }, + .names = "{.col}_mpd" + ), + A = rowSums(dplyr::pick(dplyr::ends_with("_mpd"))) / 8 + ) |> + dplyr::select(-dplyr::ends_with("_mpd")) +} + +# SOCIAL NETWORK QUANTITY +derive_B <- function(df) { + expected_cols <- paste( + "SN", + c( + "CHILD", + "FRND", + "NEIBR", + "SIBLIV", + "RELLIV", + "PERWSCH", + "PERCOM", + "PERACT" + ), + "NB", + sep = "_" + ) + validate_cols(df, expected_cols) + + df |> + dplyr::mutate( + # all of their min is 0 + SN_CHILD_NB_mpd = rev_min_max(.data$SN_CHILD_NB, max = 20), + SN_FRND_NB_mpd = rev_min_max(.data$SN_FRND_NB, max = 90), + SN_NEIBR_NB_mpd = rev_min_max(.data$SN_NEIBR_NB, max = 90), + SN_SIBLIV_NB_mpd = rev_min_max(.data$SN_SIBLIV_NB, max = 50), + SN_RELLIV_NB_mpd = rev_min_max(.data$SN_RELLIV_NB, max = 100), + SN_PERWSCH_NB_mpd = rev_min_max(.data$SN_PERWSCH_NB, max = 100), + SN_PERCOM_NB_mpd = rev_min_max(.data$SN_PERCOM_NB, max = 100), + SN_PERACT_NB_mpd = rev_min_max(.data$SN_PERACT_NB, max = 100), + B = rowSums(dplyr::pick(dplyr::ends_with("mpd"))) / 8 + ) |> + dplyr::select(-dplyr::ends_with("mpd")) +} + +rev_min_max <- function(NB, max) { + 10 - ((10 * NB) / max) +} + +# LAST TIME VISITED +derive_C <- function(df) { + expected_cols <- paste0( + "SN_SEE", + c("CHILD", "SIB", "REL", "FRND", "NEIBR") + ) + validate_cols(df, expected_cols) + + df |> + dplyr::mutate( + dplyr::across( + .cols = expected_cols, + .fns = \(x) { + dplyr::case_when( + x == 1 ~ 0, # within the last day or two + x == 2 ~ 2, # within the last week or two + x == 3 ~ 4, # within the past month + x == 4 ~ 6, # within the past 6 months + x == 5 ~ 8, # within the past year + x %in% c(6, 7) ~ 10, # more than 1 year ago; everyone lives in household + .default = NA + ) + }, + .names = "{.col}_mpd" + ), + C = rowSums(dplyr::pick(dplyr::ends_with("mpd"))) / 5 + ) |> + dplyr::select(-dplyr::ends_with("mpd")) +} + +#' Convert "Number of people living in household" into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `SN_LIVH_NB`. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `D`, dichotomized to 0 or 10. +#' +#' @details +#' Helper function for [derive_SO()]. +#' +#' @keywords internal +derive_D <- function(df) { + validate_cols(df, "SN_LIVH_NB") + + df |> + dplyr::mutate( + # SN_LIVH_NB excludes the respondent + D = dplyr::if_else(.data$SN_LIVH_NB == 0, 10, 0, missing = NA) + ) +} + +#' Convert "Marital status" into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `SDC_MRTL`, where 2 = Married or living with common-law partner. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `E`, dichotomized to 0 or 10. +#' +#' @details +#' Helper function for [derive_SO()]. +#' +#' @keywords internal +derive_E <- function(df) { + validate_cols(df, "SDC_MRTL") + + df |> + dplyr::mutate( + E = dplyr::if_else(.data$SDC_MRTL == 2, 0, 10, missing = NA) + ) +} + +#' Compute the functional objective dimension of the CLSA-SII +#' +#' @param df A data frame containing cleaned survey data. Must include the +#' following columns, each corresponding to a transformed subscale score the +#' Medical Outcomes Study (MOS) Social Support Survey: +#' +#' * `SSA_DPAFF`: Affection +#' * `SSA_DPEMO`: Emotional and informational support +#' * `SSA_DPSOC`: Positive social interaction +#' * `SSA_DPTNG`: Tangible support +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `FO`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_sii()]. +#' +#' @keywords internal +derive_FO <- function(df) { + FO <- df |> + derive_x() |> # affection + derive_y() |> # emotional and informational support + derive_z() |> # positive social interaction + derive_infinity() |> # tangible support + dplyr::mutate( + FO = (.data$x + .data$y + .data$z + .data$infinity) / 4 + ) |> + dplyr::select(-dplyr::all_of(c("x", "y", "z", "infinity"))) +} + +#' Convert "Affection" into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `SSA_DPAFF` (Affection subscale of the MOS Social Support Survey). This +#' function expects `SSA_DPAFF` to be pre-transformed by CLSA. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `x`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_FO()]. +#' +#' @keywords internal +derive_x <- function(df) { + validate_cols(df, "SSA_DPAFF") + + df |> + dplyr::mutate( + SUM_AFF = ((12 * .data$SSA_DPAFF) / 100) + 3, + x = 10 * ((15 - .data$SUM_AFF) / (15 - 3)) + ) |> + dplyr::select(-.data$SUM_AFF) +} + +#' Convert "Emotional and Informational Support" into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `SSA_DPEMO` (Emotional and Informational Support subscale of the MOS Social +#' Support Survey). This function expects `SSA_DPEMO` to be pre-transformed by +#' CLSA. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `y`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_FO()]. +#' +#' @keywords internal +derive_y <- function(df) { + validate_cols(df, "SSA_DPEMO") + + df |> + dplyr::mutate( + SUM_EMO = ((32 * .data$SSA_DPEMO) / 100) + 8, + y = 10 * ((40 - .data$SUM_EMO) / (40 - 8)) + ) |> + dplyr::select(-.data$SUM_EMO) +} + +#' Convert "Positive Social Interaction" into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `SSA_DPSOC` (Positive Social Interaction subscale of the MOS Social Support +#' Survey). This function expects `SSA_DPSOC` to be pre-transformed by CLSA. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `z`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_FO()]. +#' +#' @keywords internal +derive_z <- function(df) { + validate_cols(df, "SSA_DPSOC") + + df |> + dplyr::mutate( + SUM_SOC = ((12 * .data$SSA_DPSOC) / 100) + 3, + z = 10 * ((15 - .data$SUM_SOC) / (15 - 3)) + ) |> + dplyr::select(-.data$SUM_SOC) +} + +#' Convert "Tangible Support" into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `SSA_DPTNG` (Tangible Support subscale of the MOS Social Support Survey). +#' This function expects `SSA_DPTNG` to be pre-transformed by CLSA. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `infinity`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_FO()]. +#' +#' @keywords internal +derive_infinity <- function(df) { + validate_cols(df, "SSA_DPTNG") + + df |> + dplyr::mutate( + SUM_TNG = ((16 * .data$SSA_DPTNG) / 100) + 4, + infinity = 10 * ((20 - .data$SUM_TNG) / (20 - 4)) + ) |> + dplyr::select(-.data$SUM_TNG) +} + +#' Compute the functional subjective dimension of the CLSA-SII +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `DEP_LONLY` and `SPA_MORAC`. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `FS`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_sii()]. +#' +#' @keywords internal +derive_FS <- function(df) { + FS <- df |> + derive_alpha() |> # loneliness + derive_omega() |> # desire to participate more + dplyr::mutate( + FS = (.data$alpha + .data$omega) / 2 + ) |> + dplyr::select(-dplyr::all_of(c("alpha", "omega"))) +} + +#' Convert "How often did you feel lonely" into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `DEP_LONLY`. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `alpha`, dichotomized to 0 to 10. +#' +#' @details +#' Helper function for [derive_FS()]. +#' +#' @keywords internal +derive_alpha <- function(df) { + validate_cols(df, "DEP_LONLY") + + df |> + dplyr::mutate( + alpha = dplyr::case_when( + DEP_LONLY == 1 ~ 10, # all of the time (5-7 days) + DEP_LONLY == 2 ~ 6.67, # occasionally (3-4 days) + DEP_LONLY == 3 ~ 3.33, # some of the time (1-2 days) + DEP_LONLY == 4 ~ 0, # rarely or never (less than 1 day), + .default = NA + ) + ) +} + +#' Convert "Desire to participate in more activities" into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include +#' `SPA_MORAC`, where 1 = Yes and 2 = No. +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `omega`, dichotomized to 0 or 10. +#' +#' @details +#' Helper function for [derive_FS()]. +#' +#' @keywords internal +derive_omega <- function(df) { + validate_cols(df, "SPA_MORAC") + + df |> + dplyr::mutate( + omega = dplyr::if_else(.data$SPA_MORAC == 1, 10, 0, missing = NA) + ) +} diff --git a/tests/testthat/test-derive_sii.R b/tests/testthat/test-derive_sii.R new file mode 100644 index 0000000..7c3b9fb --- /dev/null +++ b/tests/testthat/test-derive_sii.R @@ -0,0 +1,32 @@ +test_df <- data.frame( + entity_id = 12345, + SPA_OUTS = 2, + SPA_CHRCH = 5, + SPA_CLUB = 5, + SPA_EDUC = 4, + SPA_NEIBR = 5, + SPA_OTACT = 3, + SPA_SPORT = 5, + SPA_VOLUN = 5, + SN_SEECHILD = 2, + SN_SEESIB = 6, + SN_SEEREL = 2, + SN_SEEFRND = 1, + SN_SEENEIBR = 2, + SN_LIVH_NB = 0, + SDC_MRTL = 2, + SN_CHILD_NB = 2, + SN_FRND_NB = 6, + SN_NEIBR_NB = 4, + SN_SIBLIV_NB = 0, + SN_RELLIV_NB = 75, + SN_PERWSCH_NB = 7, + SN_PERCOM_NB = 0, + SN_PERACT_NB = 0, + DEP_LONLY = 4, + SPA_MORAC = 2, + SSA_DPAFF = 75, + SSA_DPEMO = 56.25, + SSA_DPTNG = 87.50, + SSA_DPSOC = 66.67 +) From 5fc06bf5503a8d491cc1e6e46c44aa50667ccc02 Mon Sep 17 00:00:00 2001 From: Reiko Okamoto Date: Thu, 30 Jul 2026 11:38:29 -0400 Subject: [PATCH 2/4] Finish writing main and helper functions to calculate SSI --- DESCRIPTION | 2 +- NAMESPACE | 1 + R/derive_sii.R | 182 ++++++++++++++++++++++++++++--- man/clsatools-package.Rd | 1 + man/derive_A.Rd | 35 ++++++ man/derive_B.Rd | 34 ++++++ man/derive_C.Rd | 31 ++++++ man/derive_D.Rd | 23 ++++ man/derive_E.Rd | 23 ++++ man/derive_FO.Rd | 30 +++++ man/derive_FS.Rd | 23 ++++ man/derive_SO.Rd | 35 ++++++ man/derive_alpha.Rd | 23 ++++ man/derive_infinity.Rd | 24 ++++ man/derive_omega.Rd | 23 ++++ man/derive_sii.Rd | 65 +++++++++++ man/derive_x.Rd | 24 ++++ man/derive_y.Rd | 25 +++++ man/derive_z.Rd | 24 ++++ tests/testthat/test-derive_sii.R | 32 ------ 20 files changed, 613 insertions(+), 47 deletions(-) create mode 100644 man/derive_A.Rd create mode 100644 man/derive_B.Rd create mode 100644 man/derive_C.Rd create mode 100644 man/derive_D.Rd create mode 100644 man/derive_E.Rd create mode 100644 man/derive_FO.Rd create mode 100644 man/derive_FS.Rd create mode 100644 man/derive_SO.Rd create mode 100644 man/derive_alpha.Rd create mode 100644 man/derive_infinity.Rd create mode 100644 man/derive_omega.Rd create mode 100644 man/derive_sii.Rd create mode 100644 man/derive_x.Rd create mode 100644 man/derive_y.Rd create mode 100644 man/derive_z.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 883df55..1da0494 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -11,7 +11,6 @@ Description: What the package does (one paragraph). License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 Imports: dplyr, glue, @@ -27,3 +26,4 @@ Suggests: Config/testthat/edition: 3 URL: https://big-life-lab.github.io/clsatools/, https://github.com/Big-Life-Lab/clsatools VignetteBuilder: knitr +Config/roxygen2/version: 8.0.0 diff --git a/NAMESPACE b/NAMESPACE index 9dc2dfb..b5d7fcc 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,7 @@ export(derive_adl_dcls) export(derive_cag_fpas) export(derive_cr1_frhc) export(derive_cr2_frhc) +export(derive_sii) export(derive_sls_dcls) export(find_matching_pairs) export(recode_missing) diff --git a/R/derive_sii.R b/R/derive_sii.R index e3706de..8836495 100644 --- a/R/derive_sii.R +++ b/R/derive_sii.R @@ -1,31 +1,135 @@ +#' Derive the CLSA Social Isolation Index (SII) +#' +#' @param df A data frame containing cleaned data for a survey cycle. Must +#' include all columns required to compute the structural (SO), +#' functional overall (FO), and functional support (FS) dimensions. See +#' `@examples` for a minimal working input. +#' +#' @returns +#' A data frame with the same number of rows as `df`, plus a new column +#' `SII` indicating the respondent's social isolation index score. +#' +#' @references +#' Wister A, Cosco T, Mitchell B, Menec V, Fyffe I. Development and +#' concurrent validity of a composite social isolation index for older +#' adults using the CLSA. Can J Aging. 2019;38(2):180-192. +#' doi:10.1017/S0714980818000612 +#' +#' Canadian Longitudinal Study on Aging. Derived Variables — Social Support +#' Availability (SSA). 2018. +#' \url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_ssa_11apr2018_v1.1.pdf} +#' +#' @export +#' +#' @examples +#' df <- data.frame( +#' SPA_OUTS = c(3, 3, 2), +#' SPA_CHRCH = c(3, 5, 5), +#' SPA_CLUB = c(5, 5, 5), +#' SPA_EDUC = c(4, 4, 3), +#' SPA_NEIBR = c(5, 4, 4), +#' SPA_OTACT = c(4, 3, 3), +#' SPA_SPORT = c(1, 3, 2), +#' SPA_VOLUN = c(5, 4, 5), +#' SN_CHILD_NB = c(2, 4, 0), +#' SN_FRND_NB = c(0, 0, 7), +#' SN_NEIBR_NB = c(3, 31, 2), +#' SN_SIBLIV_NB = c(5, 6, 3), +#' SN_RELLIV_NB = c(12, 60, 23), +#' SN_PERWSCH_NB = c(50, 95, 100), +#' SN_PERCOM_NB = c(0, 0, 0), +#' SN_PERACT_NB = c(4, 10, 0), +#' SN_SEECHILD = c(2, 1, NA), +#' SN_SEESIB = c(2, 3, 4), +#' SN_SEEREL = c(2, 4, 3), +#' SN_SEEFRND = c(1, 1, 1), +#' SN_SEENEIBR = c(2, 2, 6), +#' SN_LIVH_NB = c(2, 5, 0), +#' SDC_MRTL = c(2, 2, 1), +#' SSA_DPAFF = c(100, 100, 50), +#' SSA_DPEMO = c(62.5, 34.38, 75), +#' SSA_DPSOC = c(58.33, 83.33, 75), +#' SSA_DPTNG = c(50, 43.75, 62.5), +#' DEP_LONLY = c(2, 4, 4), +#' SPA_MORAC = c(2, 2, 2) +#' ) +#' derive_sii(df) derive_sii <- function(df) { - SII <- df |> + sii <- df |> derive_SO() |> derive_FO() |> derive_FS() |> dplyr::mutate( - SII = (.data$SO + .data$FO + .data$FS) / 3 + sii = (.data$SO + .data$FO + .data$FS) / 3 ) |> dplyr::select(-dplyr::all_of(c("SO", "FO", "FS"))) - SII + sii } -# STRUCTURAL OBJECTIVE +#' Compute the structural objective dimension of the CLSA-SII +#' +#' @param df A data frame containing cleaned survey data. Must include the +#' following columns: +#' +#' * `SPA_OUTS`, `SPA_CHRCH`, `SPA_CLUB`, `SPA_EDUC`, `SPA_NEIBR`, +#' `SPA_OTACT`, `SPA_SPORT`, `SPA_VOLUN` (frequency of participation in +#' community activities) +#' * `SN_CHILD_NB`, `SN_FRND_NB`, `SN_NEIBR_NB`, `SN_SIBLIV_NB`, +#' `SN_RELLIV_NB`, `SN_PERWSCH_NB`, `SN_PERCOM_NB`, `SN_PERACT_NB` +#' (social network quantity) +#' * `SN_SEECHILD`, `SN_SEESIB`, `SN_SEEREL`, `SN_SEEFRND`, `SN_SEENEIBR` +#' (recency of visits outside household) +#' * `SN_LIVH_NB` (number of people living in household) +#' * `SDC_MRTL` (marital status) +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `SO`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_sii()]. +#' +#' @keywords internal derive_SO <- function(df) { SO <- df |> - derive_A() |> - derive_B() |> - derive_C() |> - derive_D() |> - derive_E() |> + derive_A() |> # frequency of participation in community activities + derive_B() |> # social network quantity + derive_C() |> # recency of visits outside household + derive_D() |> # number of people living in household + derive_E() |> # marital status dplyr::mutate( SO = (.data$A + .data$B + .data$C + .data$D + .data$E) / 5 ) |> dplyr::select(-dplyr::all_of(c("A", "B", "C", "D", "E"))) + + SO } -# COMMUNITY PARTICIPATION +#' Summarize "Community participation" survey items into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include the +#' following columns, each indicating frequency of participation in an +#' activity: +#' +#' * `SPA_OUTS`: Activities with family/friends outside of household +#' * `SPA_CHRCH`: Church or religious activities +#' * `SPA_CLUB`: Service club or fraternal organization activities +#' * `SPA_EDUC`: Educational or cultural activities +#' * `SPA_NEIBR`: Neighbourhood, community, or professional association +#' activities +#' * `SPA_OTACT`: Any other recreational activities involving others +#' * `SPA_SPORT`: Sports or physical activities involving others +#' * `SPA_VOLUN`: Volunteer or charity work +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `A`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_SO()]. +#' +#' @keywords internal derive_A <- function(df) { expected_cols <- paste0( "SPA_", @@ -54,7 +158,29 @@ derive_A <- function(df) { dplyr::select(-dplyr::ends_with("_mpd")) } -# SOCIAL NETWORK QUANTITY +#' Summarize "Social network quantity" survey items into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include the +#' following columns, each indicating a count of people in the respondent's +#' network: +#' +#' * `SN_CHILD_NB`: Living children +#' * `SN_FRND_NB`: Close friends +#' * `SN_NEIBR_NB`: Neighbours +#' * `SN_SIBLIV_NB`: Living siblings +#' * `SN_RELLIV_NB`: Living relatives +#' * `SN_PERWSCH_NB`: People known through work or school +#' * `SN_PERCOM_NB`: People known through community involvement +#' * `SN_PERACT_NB`: People known through other activities +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `B`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_SO()]. +#' +#' @keywords internal derive_B <- function(df) { expected_cols <- paste( "SN", @@ -75,7 +201,7 @@ derive_B <- function(df) { df |> dplyr::mutate( - # all of their min is 0 + # apply reverse min-max scaling; all min is 0 SN_CHILD_NB_mpd = rev_min_max(.data$SN_CHILD_NB, max = 20), SN_FRND_NB_mpd = rev_min_max(.data$SN_FRND_NB, max = 90), SN_NEIBR_NB_mpd = rev_min_max(.data$SN_NEIBR_NB, max = 90), @@ -89,11 +215,31 @@ derive_B <- function(df) { dplyr::select(-dplyr::ends_with("mpd")) } +#' @noRd rev_min_max <- function(NB, max) { 10 - ((10 * NB) / max) } -# LAST TIME VISITED +#' Summarize "Last time visited" survey items into a 0-10 score +#' +#' @param df A data frame containing cleaned survey data. Must include the +#' following columns, each indicating how recently the respondent last visited +#' someone outside their household: +#' +#' * `SN_SEECHILD`: Children +#' * `SN_SEESIB`: Siblings +#' * `SN_SEEREL`: Relatives +#' * `SN_SEEFRND`: Close friends +#' * `SN_SEENEIBR`: Neighbours +#' +#' @returns +#' A data frame with the same number of rows as `df`, with a new column +#' `C`, ranging from 0 to 10. +#' +#' @details +#' Helper function for [derive_SO()]. +#' +#' @keywords internal derive_C <- function(df) { expected_cols <- paste0( "SN_SEE", @@ -112,7 +258,7 @@ derive_C <- function(df) { x == 3 ~ 4, # within the past month x == 4 ~ 6, # within the past 6 months x == 5 ~ 8, # within the past year - x %in% c(6, 7) ~ 10, # more than 1 year ago; everyone lives in household + x %in% c(6, 7) ~ 10, # more than 1 year ago; NA everyone lives in household .default = NA ) }, @@ -197,6 +343,8 @@ derive_FO <- function(df) { FO = (.data$x + .data$y + .data$z + .data$infinity) / 4 ) |> dplyr::select(-dplyr::all_of(c("x", "y", "z", "infinity"))) + + FO } #' Convert "Affection" into a 0-10 score @@ -219,6 +367,7 @@ derive_x <- function(df) { df |> dplyr::mutate( SUM_AFF = ((12 * .data$SSA_DPAFF) / 100) + 3, + # apply reverse min-max scaling x = 10 * ((15 - .data$SUM_AFF) / (15 - 3)) ) |> dplyr::select(-.data$SUM_AFF) @@ -245,6 +394,7 @@ derive_y <- function(df) { df |> dplyr::mutate( SUM_EMO = ((32 * .data$SSA_DPEMO) / 100) + 8, + # apply reverse min-max scaling y = 10 * ((40 - .data$SUM_EMO) / (40 - 8)) ) |> dplyr::select(-.data$SUM_EMO) @@ -270,6 +420,7 @@ derive_z <- function(df) { df |> dplyr::mutate( SUM_SOC = ((12 * .data$SSA_DPSOC) / 100) + 3, + # apply reverse min-max scaling z = 10 * ((15 - .data$SUM_SOC) / (15 - 3)) ) |> dplyr::select(-.data$SUM_SOC) @@ -295,6 +446,7 @@ derive_infinity <- function(df) { df |> dplyr::mutate( SUM_TNG = ((16 * .data$SSA_DPTNG) / 100) + 4, + # apply reverse min-max scaling infinity = 10 * ((20 - .data$SUM_TNG) / (20 - 4)) ) |> dplyr::select(-.data$SUM_TNG) @@ -321,6 +473,8 @@ derive_FS <- function(df) { FS = (.data$alpha + .data$omega) / 2 ) |> dplyr::select(-dplyr::all_of(c("alpha", "omega"))) + + FS } #' Convert "How often did you feel lonely" into a 0-10 score diff --git a/man/clsatools-package.Rd b/man/clsatools-package.Rd index 4d89ebf..2c6d1a2 100644 --- a/man/clsatools-package.Rd +++ b/man/clsatools-package.Rd @@ -23,6 +23,7 @@ Useful links: Authors: \itemize{ + \item Reiko Okamoto \email{rokamoto@ohri.ca} (\href{https://orcid.org/0009-0006-3293-7940}{ORCID}) \item Wenshan Li \email{wensli@toh.ca} (\href{https://orcid.org/0000-0002-6225-5549}{ORCID}) } diff --git a/man/derive_A.Rd b/man/derive_A.Rd new file mode 100644 index 0000000..482d41a --- /dev/null +++ b/man/derive_A.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_A} +\alias{derive_A} +\title{Summarize "Community participation" survey items into a 0-10 score} +\usage{ +derive_A(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include the +following columns, each indicating frequency of participation in an +activity: +\itemize{ +\item \code{SPA_OUTS}: Activities with family/friends outside of household +\item \code{SPA_CHRCH}: Church or religious activities +\item \code{SPA_CLUB}: Service club or fraternal organization activities +\item \code{SPA_EDUC}: Educational or cultural activities +\item \code{SPA_NEIBR}: Neighbourhood, community, or professional association +activities +\item \code{SPA_OTACT}: Any other recreational activities involving others +\item \code{SPA_SPORT}: Sports or physical activities involving others +\item \code{SPA_VOLUN}: Volunteer or charity work +}} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{A}, ranging from 0 to 10. +} +\description{ +Summarize "Community participation" survey items into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_SO]{derive_SO()}}. +} +\keyword{internal} diff --git a/man/derive_B.Rd b/man/derive_B.Rd new file mode 100644 index 0000000..c18294c --- /dev/null +++ b/man/derive_B.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_B} +\alias{derive_B} +\title{Summarize "Social network quantity" survey items into a 0-10 score} +\usage{ +derive_B(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include the +following columns, each indicating a count of people in the respondent's +network: +\itemize{ +\item \code{SN_CHILD_NB}: Living children +\item \code{SN_FRND_NB}: Close friends +\item \code{SN_NEIBR_NB}: Neighbours +\item \code{SN_SIBLIV_NB}: Living siblings +\item \code{SN_RELLIV_NB}: Living relatives +\item \code{SN_PERWSCH_NB}: People known through work or school +\item \code{SN_PERCOM_NB}: People known through community involvement +\item \code{SN_PERACT_NB}: People known through other activities +}} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{B}, ranging from 0 to 10. +} +\description{ +Summarize "Social network quantity" survey items into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_SO]{derive_SO()}}. +} +\keyword{internal} diff --git a/man/derive_C.Rd b/man/derive_C.Rd new file mode 100644 index 0000000..8c34437 --- /dev/null +++ b/man/derive_C.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_C} +\alias{derive_C} +\title{Summarize "Last time visited" survey items into a 0-10 score} +\usage{ +derive_C(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include the +following columns, each indicating how recently the respondent last visited +someone outside their household: +\itemize{ +\item \code{SN_SEECHILD}: Children +\item \code{SN_SEESIB}: Siblings +\item \code{SN_SEEREL}: Relatives +\item \code{SN_SEEFRND}: Close friends +\item \code{SN_SEENEIBR}: Neighbours +}} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{C}, ranging from 0 to 10. +} +\description{ +Summarize "Last time visited" survey items into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_SO]{derive_SO()}}. +} +\keyword{internal} diff --git a/man/derive_D.Rd b/man/derive_D.Rd new file mode 100644 index 0000000..0858dfc --- /dev/null +++ b/man/derive_D.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_D} +\alias{derive_D} +\title{Convert "Number of people living in household" into a 0-10 score} +\usage{ +derive_D(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{SN_LIVH_NB}.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{D}, dichotomized to 0 or 10. +} +\description{ +Convert "Number of people living in household" into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_SO]{derive_SO()}}. +} +\keyword{internal} diff --git a/man/derive_E.Rd b/man/derive_E.Rd new file mode 100644 index 0000000..1f80228 --- /dev/null +++ b/man/derive_E.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_E} +\alias{derive_E} +\title{Convert "Marital status" into a 0-10 score} +\usage{ +derive_E(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{SDC_MRTL}, where 2 = Married or living with common-law partner.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{E}, dichotomized to 0 or 10. +} +\description{ +Convert "Marital status" into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_SO]{derive_SO()}}. +} +\keyword{internal} diff --git a/man/derive_FO.Rd b/man/derive_FO.Rd new file mode 100644 index 0000000..e5ed1f7 --- /dev/null +++ b/man/derive_FO.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_FO} +\alias{derive_FO} +\title{Compute the functional objective dimension of the CLSA-SII} +\usage{ +derive_FO(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include the +following columns, each corresponding to a transformed subscale score the +Medical Outcomes Study (MOS) Social Support Survey: +\itemize{ +\item \code{SSA_DPAFF}: Affection +\item \code{SSA_DPEMO}: Emotional and informational support +\item \code{SSA_DPSOC}: Positive social interaction +\item \code{SSA_DPTNG}: Tangible support +}} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{FO}, ranging from 0 to 10. +} +\description{ +Compute the functional objective dimension of the CLSA-SII +} +\details{ +Helper function for \code{\link[=derive_sii]{derive_sii()}}. +} +\keyword{internal} diff --git a/man/derive_FS.Rd b/man/derive_FS.Rd new file mode 100644 index 0000000..8ac253d --- /dev/null +++ b/man/derive_FS.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_FS} +\alias{derive_FS} +\title{Compute the functional subjective dimension of the CLSA-SII} +\usage{ +derive_FS(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{DEP_LONLY} and \code{SPA_MORAC}.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{FS}, ranging from 0 to 10. +} +\description{ +Compute the functional subjective dimension of the CLSA-SII +} +\details{ +Helper function for \code{\link[=derive_sii]{derive_sii()}}. +} +\keyword{internal} diff --git a/man/derive_SO.Rd b/man/derive_SO.Rd new file mode 100644 index 0000000..1ba0e61 --- /dev/null +++ b/man/derive_SO.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_SO} +\alias{derive_SO} +\title{Compute the structural objective dimension of the CLSA-SII} +\usage{ +derive_SO(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include the +following columns: +\itemize{ +\item \code{SPA_OUTS}, \code{SPA_CHRCH}, \code{SPA_CLUB}, \code{SPA_EDUC}, \code{SPA_NEIBR}, +\code{SPA_OTACT}, \code{SPA_SPORT}, \code{SPA_VOLUN} (frequency of participation in +community activities) +\item \code{SN_CHILD_NB}, \code{SN_FRND_NB}, \code{SN_NEIBR_NB}, \code{SN_SIBLIV_NB}, +\code{SN_RELLIV_NB}, \code{SN_PERWSCH_NB}, \code{SN_PERCOM_NB}, \code{SN_PERACT_NB} +(social network quantity) +\item \code{SN_SEECHILD}, \code{SN_SEESIB}, \code{SN_SEEREL}, \code{SN_SEEFRND}, \code{SN_SEENEIBR} +(recency of visits outside household) +\item \code{SN_LIVH_NB} (number of people living in household) +\item \code{SDC_MRTL} (marital status) +}} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{SO}, ranging from 0 to 10. +} +\description{ +Compute the structural objective dimension of the CLSA-SII +} +\details{ +Helper function for \code{\link[=derive_sii]{derive_sii()}}. +} +\keyword{internal} diff --git a/man/derive_alpha.Rd b/man/derive_alpha.Rd new file mode 100644 index 0000000..6175f4b --- /dev/null +++ b/man/derive_alpha.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_alpha} +\alias{derive_alpha} +\title{Convert "How often did you feel lonely" into a 0-10 score} +\usage{ +derive_alpha(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{DEP_LONLY}.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{alpha}, dichotomized to 0 to 10. +} +\description{ +Convert "How often did you feel lonely" into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_FS]{derive_FS()}}. +} +\keyword{internal} diff --git a/man/derive_infinity.Rd b/man/derive_infinity.Rd new file mode 100644 index 0000000..a2b7087 --- /dev/null +++ b/man/derive_infinity.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_infinity} +\alias{derive_infinity} +\title{Convert "Tangible Support" into a 0-10 score} +\usage{ +derive_infinity(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{SSA_DPTNG} (Tangible Support subscale of the MOS Social Support Survey). +This function expects \code{SSA_DPTNG} to be pre-transformed by CLSA.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{infinity}, ranging from 0 to 10. +} +\description{ +Convert "Tangible Support" into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_FO]{derive_FO()}}. +} +\keyword{internal} diff --git a/man/derive_omega.Rd b/man/derive_omega.Rd new file mode 100644 index 0000000..b11053c --- /dev/null +++ b/man/derive_omega.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_omega} +\alias{derive_omega} +\title{Convert "Desire to participate in more activities" into a 0-10 score} +\usage{ +derive_omega(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{SPA_MORAC}, where 1 = Yes and 2 = No.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{omega}, dichotomized to 0 or 10. +} +\description{ +Convert "Desire to participate in more activities" into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_FS]{derive_FS()}}. +} +\keyword{internal} diff --git a/man/derive_sii.Rd b/man/derive_sii.Rd new file mode 100644 index 0000000..93ee5ab --- /dev/null +++ b/man/derive_sii.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_sii} +\alias{derive_sii} +\title{Derive the CLSA Social Isolation Index (SII)} +\usage{ +derive_sii(df) +} +\arguments{ +\item{df}{A data frame containing cleaned data for a survey cycle. Must +include all columns required to compute the structural (SO), +functional overall (FO), and functional support (FS) dimensions. See +\verb{@examples} for a minimal working input.} +} +\value{ +A data frame with the same number of rows as \code{df}, plus a new column +\code{SII} indicating the respondent's social isolation index score. +} +\description{ +Derive the CLSA Social Isolation Index (SII) +} +\examples{ +df <- data.frame( + SPA_OUTS = c(3, 3, 2), + SPA_CHRCH = c(3, 5, 5), + SPA_CLUB = c(5, 5, 5), + SPA_EDUC = c(4, 4, 3), + SPA_NEIBR = c(5, 4, 4), + SPA_OTACT = c(4, 3, 3), + SPA_SPORT = c(1, 3, 2), + SPA_VOLUN = c(5, 4, 5), + SN_CHILD_NB = c(2, 4, 0), + SN_FRND_NB = c(0, 0, 7), + SN_NEIBR_NB = c(3, 31, 2), + SN_SIBLIV_NB = c(5, 6, 3), + SN_RELLIV_NB = c(12, 60, 23), + SN_PERWSCH_NB = c(50, 95, 100), + SN_PERCOM_NB = c(0, 0, 0), + SN_PERACT_NB = c(4, 10, 0), + SN_SEECHILD = c(2, 1, NA), + SN_SEESIB = c(2, 3, 4), + SN_SEEREL = c(2, 4, 3), + SN_SEEFRND = c(1, 1, 1), + SN_SEENEIBR = c(2, 2, 6), + SN_LIVH_NB = c(2, 5, 0), + SDC_MRTL = c(2, 2, 1), + SSA_DPAFF = c(100, 100, 50), + SSA_DPEMO = c(62.5, 34.38, 75), + SSA_DPSOC = c(58.33, 83.33, 75), + SSA_DPTNG = c(50, 43.75, 62.5), + DEP_LONLY = c(2, 4, 4), + SPA_MORAC = c(2, 2, 2) +) +derive_sii(df) +} +\references{ +Wister A, Cosco T, Mitchell B, Menec V, Fyffe I. Development and +concurrent validity of a composite social isolation index for older +adults using the CLSA. Can J Aging. 2019;38(2):180-192. +doi:10.1017/S0714980818000612 + +Canadian Longitudinal Study on Aging. Derived Variables — Social Support +Availability (SSA). 2018. +\url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_ssa_11apr2018_v1.1.pdf} +} diff --git a/man/derive_x.Rd b/man/derive_x.Rd new file mode 100644 index 0000000..98cb169 --- /dev/null +++ b/man/derive_x.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_x} +\alias{derive_x} +\title{Convert "Affection" into a 0-10 score} +\usage{ +derive_x(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{SSA_DPAFF} (Affection subscale of the MOS Social Support Survey). This +function expects \code{SSA_DPAFF} to be pre-transformed by CLSA.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{x}, ranging from 0 to 10. +} +\description{ +Convert "Affection" into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_FO]{derive_FO()}}. +} +\keyword{internal} diff --git a/man/derive_y.Rd b/man/derive_y.Rd new file mode 100644 index 0000000..a3c9daf --- /dev/null +++ b/man/derive_y.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_y} +\alias{derive_y} +\title{Convert "Emotional and Informational Support" into a 0-10 score} +\usage{ +derive_y(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{SSA_DPEMO} (Emotional and Informational Support subscale of the MOS Social +Support Survey). This function expects \code{SSA_DPEMO} to be pre-transformed by +CLSA.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{y}, ranging from 0 to 10. +} +\description{ +Convert "Emotional and Informational Support" into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_FO]{derive_FO()}}. +} +\keyword{internal} diff --git a/man/derive_z.Rd b/man/derive_z.Rd new file mode 100644 index 0000000..0372fda --- /dev/null +++ b/man/derive_z.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/derive_sii.R +\name{derive_z} +\alias{derive_z} +\title{Convert "Positive Social Interaction" into a 0-10 score} +\usage{ +derive_z(df) +} +\arguments{ +\item{df}{A data frame containing cleaned survey data. Must include +\code{SSA_DPSOC} (Positive Social Interaction subscale of the MOS Social Support +Survey). This function expects \code{SSA_DPSOC} to be pre-transformed by CLSA.} +} +\value{ +A data frame with the same number of rows as \code{df}, with a new column +\code{z}, ranging from 0 to 10. +} +\description{ +Convert "Positive Social Interaction" into a 0-10 score +} +\details{ +Helper function for \code{\link[=derive_FO]{derive_FO()}}. +} +\keyword{internal} diff --git a/tests/testthat/test-derive_sii.R b/tests/testthat/test-derive_sii.R index 7c3b9fb..e69de29 100644 --- a/tests/testthat/test-derive_sii.R +++ b/tests/testthat/test-derive_sii.R @@ -1,32 +0,0 @@ -test_df <- data.frame( - entity_id = 12345, - SPA_OUTS = 2, - SPA_CHRCH = 5, - SPA_CLUB = 5, - SPA_EDUC = 4, - SPA_NEIBR = 5, - SPA_OTACT = 3, - SPA_SPORT = 5, - SPA_VOLUN = 5, - SN_SEECHILD = 2, - SN_SEESIB = 6, - SN_SEEREL = 2, - SN_SEEFRND = 1, - SN_SEENEIBR = 2, - SN_LIVH_NB = 0, - SDC_MRTL = 2, - SN_CHILD_NB = 2, - SN_FRND_NB = 6, - SN_NEIBR_NB = 4, - SN_SIBLIV_NB = 0, - SN_RELLIV_NB = 75, - SN_PERWSCH_NB = 7, - SN_PERCOM_NB = 0, - SN_PERACT_NB = 0, - DEP_LONLY = 4, - SPA_MORAC = 2, - SSA_DPAFF = 75, - SSA_DPEMO = 56.25, - SSA_DPTNG = 87.50, - SSA_DPSOC = 66.67 -) From bc437f27e7bde809b654e2f1f3aab8d41a3422f1 Mon Sep 17 00:00:00 2001 From: Reiko Okamoto Date: Fri, 31 Jul 2026 10:31:25 -0400 Subject: [PATCH 3/4] Add tests for derive_sii, update docs --- R/derive_sii.R | 6 +- man/derive_alpha.Rd | 2 +- tests/testthat/test-derive_sii.R | 499 +++++++++++++++++++++++++++++++ 3 files changed, 503 insertions(+), 4 deletions(-) diff --git a/R/derive_sii.R b/R/derive_sii.R index 8836495..85577bc 100644 --- a/R/derive_sii.R +++ b/R/derive_sii.R @@ -140,7 +140,7 @@ derive_A <- function(df) { df |> dplyr::mutate( dplyr::across( - .cols = expected_cols, + .cols = dplyr::all_of(expected_cols), .fns = \(x) { dplyr::case_when( x == 1 ~ 0, # at least once a day @@ -250,7 +250,7 @@ derive_C <- function(df) { df |> dplyr::mutate( dplyr::across( - .cols = expected_cols, + .cols = dplyr::all_of(expected_cols), .fns = \(x) { dplyr::case_when( x == 1 ~ 0, # within the last day or two @@ -484,7 +484,7 @@ derive_FS <- function(df) { #' #' @returns #' A data frame with the same number of rows as `df`, with a new column -#' `alpha`, dichotomized to 0 to 10. +#' `alpha`, ranging from 0 to 10. #' #' @details #' Helper function for [derive_FS()]. diff --git a/man/derive_alpha.Rd b/man/derive_alpha.Rd index 6175f4b..e0d63af 100644 --- a/man/derive_alpha.Rd +++ b/man/derive_alpha.Rd @@ -12,7 +12,7 @@ derive_alpha(df) } \value{ A data frame with the same number of rows as \code{df}, with a new column -\code{alpha}, dichotomized to 0 to 10. +\code{alpha}, ranging from 0 to 10. } \description{ Convert "How often did you feel lonely" into a 0-10 score diff --git a/tests/testthat/test-derive_sii.R b/tests/testthat/test-derive_sii.R index e69de29..de00041 100644 --- a/tests/testthat/test-derive_sii.R +++ b/tests/testthat/test-derive_sii.R @@ -0,0 +1,499 @@ +df_A <- data.frame( + SPA_OUTS = 3, + SPA_CHRCH = 3, + SPA_CLUB = 5, + SPA_EDUC = 4, + SPA_NEIBR = 5, + SPA_OTACT = 4, + SPA_SPORT = 1, + SPA_VOLUN = 2 +) + +test_that("derive_A maps codes and averages correctly", { + result <- derive_A(df_A) + + # (5 + 5 + 10 + 7.5 + 10 + 7.5 + 0 + 2.5) / 8 = 5.9375 + expect_equal(result$A, 5.9375) +}) + +test_that("derive_A returns NA for invalid codes", { + result <- df_A |> + dplyr::mutate(SPA_OUTS = 6) |> + derive_A() + + expect_true(is.na(result$A)) +}) + +test_that("derive_A drops intermediate columns", { + result <- derive_A(df_A) + + expect_false(any(stringr::str_detect(colnames(result), "_mpd$"))) +}) + +test_that("derive_A errors if a required column is missing", { + df_A |> + dplyr::select(-SPA_OUTS) |> + derive_A() |> + expect_error() +}) + +df_B <- data.frame( + SN_CHILD_NB = 2, + SN_FRND_NB = 0, + SN_NEIBR_NB = 3, + SN_SIBLIV_NB = 5, + SN_RELLIV_NB = 12, + SN_PERWSCH_NB = 50, + SN_PERCOM_NB = 0, + SN_PERACT_NB = 4 +) + +test_that("derive_B transforms inputs and averages correctly", { + result <- derive_B(df_B) + + # (9 + 10 + 9.6667 + 9 + 8.8 + 5 + 10 + 9.6) / 8 = 8.883337 + expect_equal(result$B, 8.883337, tolerance = 1e-3) +}) + +test_that("derive_B returns NA when an input count is NA", { + result <- df_B |> + dplyr::mutate(SN_CHILD_NB = NA) |> + derive_B() + + expect_true(is.na(result$B)) +}) + +test_that("derive_B drops intermediate columns", { + result <- derive_B(df_B) + + expect_false(any(stringr::str_detect(colnames(result), "_mpd$"))) +}) + +test_that("derive_B errors if a required column is missing", { + df_B |> + dplyr::select(-SN_CHILD_NB) |> + derive_B() |> + expect_error() +}) + +df_C <- data.frame( + SN_SEECHILD = 5, + SN_SEESIB = 4, + SN_SEEREL = 2, + SN_SEEFRND = 1, + SN_SEENEIBR = 3 +) + +test_that("derive_C maps codes and averages correctly", { + result <- derive_C(df_C) + + # (8 + 6 + 2 + 0 + 4) / 5 + expect_equal(result$C, 4) +}) + +test_that("derive_C returns NA when input is NA", { + result <- df_C |> + dplyr::mutate(SN_SEECHILD = NA) |> + derive_C() + + expect_true(is.na(result$C)) +}) + +test_that("derive_C drops intermediate columns", { + result <- derive_C(df_C) + + expect_false(any(stringr::str_detect(colnames(result), "_mpd$"))) +}) + +test_that("derive_C errors if a required column is missing", { + df_C |> + dplyr::select(-SN_SEECHILD) |> + derive_C() |> + expect_error() +}) + +df_D <- data.frame( + SN_LIVH_NB = 0 +) + +test_that("derive_D maps 0 to 10 and other values to 0", { + result <- derive_D(df_D) + + expect_equal(result$D, 10) + + result_not_alone <- df_D |> + dplyr::mutate(SN_LIVH_NB = 3) |> + derive_D() + + expect_equal(result_not_alone$D, 0) +}) + +test_that("derive_D returns NA when input is NA", { + result <- df_D |> + dplyr::mutate(SN_LIVH_NB = NA) |> + derive_D() + + expect_true(is.na(result$D)) +}) + +test_that("derive_D errors if the required column is missing", { + df_D |> + dplyr::select(-SN_LIVH_NB) |> + derive_D() |> + expect_error() +}) + +df_E <- data.frame( + SDC_MRTL = 2 +) + +test_that("derive_E maps 2 to 0 and other values to 10", { + result <- derive_E(df_E) + + expect_equal(result$E, 0) + + result_not_married <- df_E |> + dplyr::mutate(SDC_MRTL = 1) |> + derive_E() + + expect_equal(result_not_married$E, 10) +}) + +test_that("derive_E returns NA when input is NA", { + result <- df_E |> + dplyr::mutate(SDC_MRTL = NA) |> + derive_E() + + expect_true(is.na(result$E)) +}) + +test_that("derive_E errors if the required column is missing", { + df_E |> + dplyr::select(-SDC_MRTL) |> + derive_E() |> + expect_error() +}) + +df_SO <- dplyr::bind_cols( + df_A, + df_B, + df_C, + df_D, + df_E +) + +test_that("derive_SO averages A-E correctly", { + result <- derive_SO(df_SO) + + # A = 5.9375, B = 8.883337, C = 4, D = 10, E = 0 + # (5.9375 + 8.883337 + 4 + 10 + 0) / 5 + expect_equal(result$SO, 5.764167, tolerance = 1e-3) +}) + +test_that("derive_SO drops intermediate columns", { + result <- derive_SO(df_SO) + + expect_false(any(c("A", "B", "C", "D", "E") %in% colnames(result))) +}) + +test_that("derive_SO errors if a required column is missing", { + df_SO |> + dplyr::select(-SDC_MRTL) |> + derive_SO() |> + expect_error() +}) + +df_x <- data.frame( + SSA_DPAFF = 50 +) + +test_that("derive_x transforms input correctly", { + result <- derive_x(df_x) + + # SUM_AFF = (12 * 50 / 100) + 3 = 9 + # x = 10 * (15 - 9) / (15 - 3) = 5 + expect_equal(result$x, 5) +}) + +test_that("derive_x is bounded 0-10 at extremes", { + low <- df_x |> dplyr::mutate(SSA_DPAFF = 0) |> derive_x() + high <- df_x |> dplyr::mutate(SSA_DPAFF = 100) |> derive_x() + + expect_equal(low$x, 10) + expect_equal(high$x, 0) +}) + +test_that("derive_x returns NA when input is NA", { + result <- df_x |> + dplyr::mutate(SSA_DPAFF = NA) |> + derive_x() + + expect_true(is.na(result$x)) +}) + +test_that("derive_x drops intermediate column", { + result <- derive_x(df_x) + + expect_false("SUM_AFF" %in% colnames(result)) +}) + +test_that("derive_x errors if the required column is missing", { + df_x |> + dplyr::select(-SSA_DPAFF) |> + derive_x() |> + expect_error() +}) + +df_y <- data.frame( + SSA_DPEMO = 50 +) + +test_that("derive_y transforms input correctly", { + result <- derive_y(df_y) + + # SUM_EMO = (32 * 50 / 100) + 8 = 24 + # y = 10 * (40 - 24) / (40 - 8) = 5 + expect_equal(result$y, 5) +}) + +test_that("derive_y is bounded 0-10 at extremes", { + low <- df_y |> dplyr::mutate(SSA_DPEMO = 0) |> derive_y() + high <- df_y |> dplyr::mutate(SSA_DPEMO = 100) |> derive_y() + + expect_equal(low$y, 10) + expect_equal(high$y, 0) +}) + +test_that("derive_y returns NA when input is NA", { + result <- df_y |> + dplyr::mutate(SSA_DPEMO = NA) |> + derive_y() + + expect_true(is.na(result$y)) +}) + +test_that("derive_y drops intermediate column", { + result <- derive_y(df_y) + + expect_false("SUM_EMO" %in% colnames(result)) +}) + +test_that("derive_y errors if the required column is missing", { + df_y |> + dplyr::select(-SSA_DPEMO) |> + derive_y() |> + expect_error() +}) + +df_z <- data.frame( + SSA_DPSOC = 50 +) + +test_that("derive_z transforms input correctly", { + result <- derive_z(df_z) + + # SUM_SOC = (12 * 50 / 100) + 3 = 9 + # z = 10 * (15 - 9) / (15 - 3) = 5 + expect_equal(result$z, 5) +}) + +test_that("derive_z is bounded 0-10 at extremes", { + low <- df_z |> dplyr::mutate(SSA_DPSOC = 0) |> derive_z() + high <- df_z |> dplyr::mutate(SSA_DPSOC = 100) |> derive_z() + + expect_equal(low$z, 10) + expect_equal(high$z, 0) +}) + +test_that("derive_z returns NA when input is NA", { + result <- df_z |> + dplyr::mutate(SSA_DPSOC = NA) |> + derive_z() + + expect_true(is.na(result$z)) +}) + +test_that("derive_z drops intermediate column", { + result <- derive_z(df_z) + + expect_false("SUM_SOC" %in% colnames(result)) +}) + +test_that("derive_z errors if the required column is missing", { + df_z |> + dplyr::select(-SSA_DPSOC) |> + derive_z() |> + expect_error() +}) + +df_infinity <- data.frame( + SSA_DPTNG = 50 +) + +test_that("derive_infinity transforms input correctly", { + result <- derive_infinity(df_infinity) + + # SUM_TNG = (16 * 50 / 100) + 4 = 12 + # infinity = 10 * (20 - 12) / (20 - 4) = 5 + expect_equal(result$infinity, 5) +}) + +test_that("derive_infinity is bounded 0-10 at extremes", { + low <- df_infinity |> dplyr::mutate(SSA_DPTNG = 0) |> derive_infinity() + high <- df_infinity |> dplyr::mutate(SSA_DPTNG = 100) |> derive_infinity() + + expect_equal(low$infinity, 10) + expect_equal(high$infinity, 0) +}) + +test_that("derive_infinity returns NA when input is NA", { + result <- df_infinity |> + dplyr::mutate(SSA_DPTNG = NA) |> + derive_infinity() + + expect_true(is.na(result$infinity)) +}) + +test_that("derive_infinity drops intermediate column", { + result <- derive_infinity(df_infinity) + + expect_false("SUM_TNG" %in% colnames(result)) +}) + +test_that("derive_infinity errors if the required column is missing", { + df_infinity |> + dplyr::select(-SSA_DPTNG) |> + derive_infinity() |> + expect_error() +}) + +df_FO <- dplyr::bind_cols(df_x, df_y, df_z, df_infinity) + +test_that("derive_FO averages x/y/z/infinity correctly", { + result <- derive_FO(df_FO) + + # x = 5, y = 5, z = 5, infinity = 5 + # (5 + 5 + 5 + 5) / 4 = 5 + expect_equal(result$FO, 5) +}) + +test_that("derive_FO drops intermediate columns", { + result <- derive_FO(df_FO) + + expect_false(any(c("x", "y", "z", "infinity") %in% colnames(result))) +}) + +test_that("derive_FO errors if a required column is missing", { + df_FO |> + dplyr::select(-SSA_DPTNG) |> + derive_FO() |> + expect_error() +}) + +df_alpha <- data.frame( + DEP_LONLY = 1 +) + +test_that("derive_alpha maps codes correctly", { + result <- derive_alpha(df_alpha) + expect_equal(result$alpha, 10) + + result2 <- df_alpha |> dplyr::mutate(DEP_LONLY = 2) |> derive_alpha() + expect_equal(result2$alpha, 6.67) + + result3 <- df_alpha |> dplyr::mutate(DEP_LONLY = 3) |> derive_alpha() + expect_equal(result3$alpha, 3.33) + + result4 <- df_alpha |> dplyr::mutate(DEP_LONLY = 4) |> derive_alpha() + expect_equal(result4$alpha, 0) +}) + +test_that("derive_alpha returns NA for invalid inputs", { + result <- df_alpha |> + dplyr::mutate(DEP_LONLY = 5) |> + derive_alpha() + + expect_true(is.na(result$alpha)) +}) + +test_that("derive_alpha errors if the required column is missing", { + df_alpha |> + dplyr::select(-DEP_LONLY) |> + derive_alpha() |> + expect_error() +}) + +df_omega <- data.frame( + SPA_MORAC = 1 +) + +test_that("derive_omega maps codes correctly", { + result <- derive_omega(df_omega) + expect_equal(result$omega, 10) + + result_no <- df_omega |> + dplyr::mutate(SPA_MORAC = 2) |> + derive_omega() + expect_equal(result_no$omega, 0) +}) + +test_that("derive_omega returns NA when input is NA", { + result <- df_omega |> + dplyr::mutate(SPA_MORAC = NA) |> + derive_omega() + + expect_true(is.na(result$omega)) +}) + +test_that("derive_omega errors if the required column is missing", { + df_omega |> + dplyr::select(-SPA_MORAC) |> + derive_omega() |> + expect_error() +}) + +df_FS <- dplyr::bind_cols(df_alpha, df_omega) + +test_that("derive_FS averages alpha/omega correctly", { + result <- derive_FS(df_FS) + + # alpha = 10, omega = 10 + # (10 + 10) / 2 = 10 + expect_equal(result$FS, 10) +}) + +test_that("derive_FS drops intermediate columns", { + result <- derive_FS(df_FS) + + expect_false(any(c("alpha", "omega") %in% colnames(result))) +}) + +test_that("derive_FS errors if a required column is missing", { + df_FS |> + dplyr::select(-SPA_MORAC) |> + derive_FS() |> + expect_error() +}) + +df_sii <- dplyr::bind_cols(df_SO, df_FO, df_FS) + +test_that("derive_sii averages SO/FO/FS correctly", { + result <- derive_sii(df_sii) + + # SO = 5.764167, FO = 5, FS = 10 + # (5.764167 + 5 + 10) / 3 = 6.921389 + expect_equal(result$sii, 6.921389, tolerance = 1e-3) +}) + +test_that("derive_sii drops intermediate columns", { + result <- derive_sii(df_sii) + + expect_false(any(c("SO", "FO", "FS") %in% colnames(result))) +}) + +test_that("derive_sii errors if a required column is missing", { + df_sii |> + dplyr::select(-SDC_MRTL) |> + derive_sii() |> + expect_error() +}) From 79d72847b6268b1bb6fa004fe6b1cf0f81cfeded Mon Sep 17 00:00:00 2001 From: Reiko Okamoto Date: Fri, 31 Jul 2026 10:32:09 -0400 Subject: [PATCH 4/4] Update CLSA reference in docs --- R/derive_adl_dcls.R | 8 +++++--- R/derive_cag_fpas.R | 6 ++++-- R/derive_cr1_frhc.R | 7 +++++-- R/derive_cr2_frhc.R | 7 +++++-- R/derive_sls_dcls.R | 6 ++++-- man/derive_adl_dcls.Rd | 5 ++++- man/derive_cag_fpas.Rd | 4 +++- man/derive_cr1_frhc.Rd | 5 ++++- man/derive_cr2_frhc.Rd | 5 ++++- man/derive_sls_dcls.Rd | 4 +++- 10 files changed, 41 insertions(+), 16 deletions(-) diff --git a/R/derive_adl_dcls.R b/R/derive_adl_dcls.R index 02c3c53..1473846 100644 --- a/R/derive_adl_dcls.R +++ b/R/derive_adl_dcls.R @@ -12,9 +12,11 @@ #' Intended for use with Follow-up 2 data, for which this variable was not #' derived by the data provider. #' -#' @references [Derived Variables - Basic Activities of Daily Living (ADL) & -#' Instrumental Activities of Daily Living -#' (IAL)](https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_adl_10aug2018.pdf) +#' @references +#' Canadian Longitudinal Study on Aging. Derived Variables — Basic +#' Activities of Daily Living (ADL) & Instrumental Activities of Daily +#' Living (IAL). 2018. +#' \url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_adl_10aug2018.pdf} #' #' @export #' @examples diff --git a/R/derive_cag_fpas.R b/R/derive_cag_fpas.R index d5b55bc..6d11239 100644 --- a/R/derive_cag_fpas.R +++ b/R/derive_cag_fpas.R @@ -29,8 +29,10 @@ #' `CAG_HLT_HC`) are excluded from the function because they are unavailable at #' Follow-up 2. #' -#' @references [Derived Variables – Care Giving -#' (CAG)](https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_caregiv_v1.4_2019feb01.pdf) +#' @references +#' Canadian Longitudinal Study on Aging. Derived Variables — Care Giving +#' (CAG). 2019. +#' \url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_caregiv_v1.4_2019feb01.pdf} #' #' @export #' @examples diff --git a/R/derive_cr1_frhc.R b/R/derive_cr1_frhc.R index a20aa79..132da13 100644 --- a/R/derive_cr1_frhc.R +++ b/R/derive_cr1_frhc.R @@ -27,8 +27,11 @@ #' algorithm (`CR1_PRO_MC` and `CR1_PRO_MISSING`) are excluded from the function #' because they are unavailable at Follow-up 2. #' -#' @references [Derived Variables – Care Receiving 1/Formal Care -#' (CR1)](https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_cr_4dec2017.pdf) +#' @references +#' Canadian Longitudinal Study on Aging. Derived Variables — Care +#' Receiving 1/Formal Care (CR1) & Care Receiving 2/Informal Care (CR2). +#' 2017. +#' \url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_cr_4dec2017.pdf} #' #' @export #' @examples diff --git a/R/derive_cr2_frhc.R b/R/derive_cr2_frhc.R index e47925a..aa724bb 100644 --- a/R/derive_cr2_frhc.R +++ b/R/derive_cr2_frhc.R @@ -25,8 +25,11 @@ #' algorithm (`CR2_FAM_CS`, `CR2_FAM_MB`, `CR2_FAM_MISSING`, and `CR2_FAM_MN`) #' are excluded from the function because they are unavailable at Follow-up 2. #' -#' @references [Derived Variables – Care Receiving 2/Informal Care (CR2) -#' ](https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_cr_4dec2017.pdf) +#' @references +#' Canadian Longitudinal Study on Aging. Derived Variables — Care +#' Receiving 1/Formal Care (CR1) & Care Receiving 2/Informal Care (CR2). +#' 2017. +#' \url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_cr_4dec2017.pdf} #' #' @export #' @examples diff --git a/R/derive_sls_dcls.R b/R/derive_sls_dcls.R index 653d303..8851cb6 100644 --- a/R/derive_sls_dcls.R +++ b/R/derive_sls_dcls.R @@ -6,8 +6,10 @@ #' A data frame with the same number of rows as `df` plus a new column #' `SLS_DCLS` indicating the respondent's satisfaction with life. #' -#' @references [Derived Variables – Satisfaction With Life (SLS) -#' ](https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_sls_4dec2017.pdf) +#' @references +#' Canadian Longitudinal Study on Aging. Derived Variables — Satisfaction +#' With Life (SLS). 2017. +#' \url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_sls_4dec2017.pdf} #' #' @export #' @examples diff --git a/man/derive_adl_dcls.Rd b/man/derive_adl_dcls.Rd index 03957e7..23d6e03 100644 --- a/man/derive_adl_dcls.Rd +++ b/man/derive_adl_dcls.Rd @@ -49,5 +49,8 @@ df <- setNames( derive_adl_dcls(df) } \references{ -\href{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_adl_10aug2018.pdf}{Derived Variables - Basic Activities of Daily Living (ADL) & Instrumental Activities of Daily Living (IAL)} +Canadian Longitudinal Study on Aging. Derived Variables — Basic +Activities of Daily Living (ADL) & Instrumental Activities of Daily +Living (IAL). 2018. +\url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_adl_10aug2018.pdf} } diff --git a/man/derive_cag_fpas.Rd b/man/derive_cag_fpas.Rd index 67a7f4e..da806d6 100644 --- a/man/derive_cag_fpas.Rd +++ b/man/derive_cag_fpas.Rd @@ -59,5 +59,7 @@ df <- data.frame( derive_cag_fpas(df) } \references{ -\href{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_caregiv_v1.4_2019feb01.pdf}{Derived Variables – Care Giving (CAG)} +Canadian Longitudinal Study on Aging. Derived Variables — Care Giving +(CAG). 2019. +\url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_caregiv_v1.4_2019feb01.pdf} } diff --git a/man/derive_cr1_frhc.Rd b/man/derive_cr1_frhc.Rd index 6b16d12..8c45340 100644 --- a/man/derive_cr1_frhc.Rd +++ b/man/derive_cr1_frhc.Rd @@ -56,5 +56,8 @@ df <- data.frame( derive_cr1_frhc(df) } \references{ -\href{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_cr_4dec2017.pdf}{Derived Variables – Care Receiving 1/Formal Care (CR1)} +Canadian Longitudinal Study on Aging. Derived Variables — Care +Receiving 1/Formal Care (CR1) & Care Receiving 2/Informal Care (CR2). +2017. +\url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_cr_4dec2017.pdf} } diff --git a/man/derive_cr2_frhc.Rd b/man/derive_cr2_frhc.Rd index 51139fa..7baf882 100644 --- a/man/derive_cr2_frhc.Rd +++ b/man/derive_cr2_frhc.Rd @@ -52,5 +52,8 @@ df <- data.frame( derive_cr2_frhc(df) } \references{ -\href{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_cr_4dec2017.pdf}{Derived Variables – Care Receiving 2/Informal Care (CR2) } +Canadian Longitudinal Study on Aging. Derived Variables — Care +Receiving 1/Formal Care (CR1) & Care Receiving 2/Informal Care (CR2). +2017. +\url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_cr_4dec2017.pdf} } diff --git a/man/derive_sls_dcls.Rd b/man/derive_sls_dcls.Rd index b5bd81e..13aaca9 100644 --- a/man/derive_sls_dcls.Rd +++ b/man/derive_sls_dcls.Rd @@ -38,5 +38,7 @@ df <- data.frame( derive_sls_dcls(df) } \references{ -\href{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_sls_4dec2017.pdf}{Derived Variables – Satisfaction With Life (SLS) } +Canadian Longitudinal Study on Aging. Derived Variables — Satisfaction +With Life (SLS). 2017. +\url{https://www.clsa-elcv.ca/wp-content/uploads/2023/06/dv_sls_4dec2017.pdf} }