From 5f68290276d726e9e088e546583d877ea77e9342 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 16:54:12 -0400 Subject: [PATCH 01/11] extendparser --- R/readPedigree.R | 26 +++++++++++++++++++++++++- tests/testthat/test-readPedigrees.R | 16 ++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/R/readPedigree.R b/R/readPedigree.R index d79bb230..2dd48c3d 100644 --- a/R/readPedigree.R +++ b/R/readPedigree.R @@ -579,8 +579,32 @@ countPatternRows <- function(file) { #' Read Wiki Family Tree #' #' @param text A character string containing the text of a family tree in wiki format. +#' @param verbose A logical value indicating whether to print messages. +#' @param file_path The path to the file containing the family tree. +#' @param ... Additional arguments (not used). +#' +#' @return A list containing the summary, members, structure, and relationships of the family tree. #' @export -readWikifamilytree <- function(text) { +readWikifamilytree <- function(text=NULL, verbose = FALSE, file_path = NULL, ...) { + # Checks + if (is.null(text) && is.null(file_path)) { + stop("Either 'text' or 'file_path' must be provided.") + } + # read from file if provided +if (!is.null(file_path)){ + + if (!file.exists(file_path)) stop("File does not exist: ", file_path) + + if (verbose) { + print(paste("Reading file:", file_path)) + } + file <- data.frame(X1 = readLines(file_path)) + file_length <- nrow(file) + if (verbose) { + print(paste0("File is ", file_length, " lines long")) + } + text <- file$X1 +} # Extract summary text summary_text <- extractSummaryText(text) diff --git a/tests/testthat/test-readPedigrees.R b/tests/testthat/test-readPedigrees.R index 94cbcb28..5086b83c 100644 --- a/tests/testthat/test-readPedigrees.R +++ b/tests/testthat/test-readPedigrees.R @@ -181,7 +181,7 @@ test_that("if file does not exist, readGedcom throws an error", { # readWikifamilytree -test_that("readWikifamilytree reads a simple file correctly", { +test_that("readWikifamilytree reads a string correctly", { # Create a temporary WikiFamilyTree file for testing # Example usage family_tree_text <- "{{familytree/start |summary=I have a brother Joe and a little sister: my mom married my dad, and my dad's parents were Grandma and Grandpa; they had another child, Aunt Daisy.}} @@ -192,7 +192,7 @@ test_that("readWikifamilytree reads a simple file correctly", { {{familytree | JOE | | ME | | SIS | | | JOE=My brother Joe|ME='''Me!'''|SIS=My little sister}} {{familytree/end}}" - result <- readWikifamilytree(family_tree_text) + result <- readWikifamilytree(text=family_tree_text) # list( # summary = summary_text, @@ -205,3 +205,15 @@ test_that("readWikifamilytree reads a simple file correctly", { "I have a brother Joe and a little sister: my mom married my dad, and my dad's parents were Grandma and Grandpa; they had another child, Aunt Daisy." ) }) + + +# read E:/Dropbox/Lab/Research/Projects/2024/BGMiscJoss/BGmisc_main/data-raw/Targaryen tree Dance.txt + +test_that("readWikifamilytree reads a file correctly", { + # Create a temporary WikiFamilyTree file for testing + # Example usage + family_tree_file_path <- "data-raw/Targaryen tree Dance.txt" #system.file("extdata", "Targaryen tree Dance.txt", package = "BGmisc") + + result <- readWikifamilytree(file_path=family_tree_file_path) + +}) From bb9f66b86ff8c611350f12417048b4346a47f85c Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 16:58:51 -0400 Subject: [PATCH 02/11] split Update readWikifamilytree.R --- R/readPedigree.R | 2 +- R/readWikifamilytree.R | 198 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 R/readWikifamilytree.R diff --git a/R/readPedigree.R b/R/readPedigree.R index 2dd48c3d..3faafa07 100644 --- a/R/readPedigree.R +++ b/R/readPedigree.R @@ -603,7 +603,7 @@ if (!is.null(file_path)){ if (verbose) { print(paste0("File is ", file_length, " lines long")) } - text <- file$X1 + text <- paste0(file$X1, collapse = "\n") } # Extract summary text diff --git a/R/readWikifamilytree.R b/R/readWikifamilytree.R new file mode 100644 index 00000000..6d14c41a --- /dev/null +++ b/R/readWikifamilytree.R @@ -0,0 +1,198 @@ +#' Read Wiki Family Tree +#' +#' @param text A character string containing the text of a family tree in wiki format. +#' @param verbose A logical value indicating whether to print messages. +#' @param file_path The path to the file containing the family tree. +#' @param ... Additional arguments (not used). +#' +#' @return A list containing the summary, members, structure, and relationships of the family tree. +#' @export +readWikifamilytree <- function(text=NULL, verbose = FALSE, file_path = NULL, ...) { + # Checks + if (is.null(text) && is.null(file_path)) { + stop("Either 'text' or 'file_path' must be provided.") + } + # read from file if provided +if (!is.null(file_path)){ + + if (!file.exists(file_path)) stop("File does not exist: ", file_path) + + if (verbose) { + print(paste("Reading file:", file_path)) + } + file <- data.frame(X1 = readLines(file_path)) + file_length <- nrow(file) + if (verbose) { + print(paste0("File is ", file_length, " lines long")) + } + text <- paste0(file$X1, collapse = "\n") +} + # Extract summary text + + summary_text <- extractSummaryText(text) + # Extract all lines defining the family tree + tree_lines <- unlist(stringr::str_extract_all(text, "\\{\\{familytree.*?\\}\\}")) + tree_lines <- tree_lines[!stringr::str_detect(tree_lines, "start|end")] # Remove start/end markers + tree_lines <- gsub("\\{\\{familytree(.*?)\\}\\}", "\\1", tree_lines) # Remove wrapping markup + + # Convert tree structure into a coordinate grid (preserves symbols!) + tree_df <- parseTree(tree_lines) + + # Identify columns that start with "Y" + cols_to_pivot <- grep("^Y", names(tree_df), value = TRUE) + + # Reshape from wide to long format + tree_long <- makeLongTree(tree_df, cols_to_pivot) + + # Extract member definitions + members_df <- matchMembers(text) + members_df$id <- paste0("P", seq_len(nrow(members_df))) # Assign unique person IDs + + # Merge names into the tree structure (keeping all symbols!) + tree_long <- merge(tree_long, members_df, by.x = "Value", by.y = "identifier", all.x = TRUE) + + tree_long$DisplayName <- ifelse(!is.na(tree_long$name), tree_long$name, tree_long$Value) # Use name if available + + # parse relationships and infer them + + relationships_df <- parseRelationships(tree_long) + + # relationships_df <- processParents(tree_long, datasource = "wiki") + + + + # Return structured table of the family tree (symbols included) + list( + summary = summary_text, + members = members_df, + structure = tree_long, + relationships = relationships_df + ) +} + +#' Make Long Tree +#' @param tree_df A data frame containing the tree structure. +#' @param cols_to_pivot A character vector of column names to pivot. +#' @return A long data frame containing the tree structure. +#' @keywords internal +makeLongTree <- function(tree_df, cols_to_pivot) { + tree_long <- stats::reshape(tree_df, + varying = cols_to_pivot, + v.names = "Value", + timevar = "Column", + times = cols_to_pivot, + idvar = setdiff(names(tree_df), cols_to_pivot), + direction = "long" + ) + + tree_long <- tree_long[!is.na(tree_long$Value), ] + tree_long$Value <- stringr::str_trim(tree_long$Value) + tree_long$Column <- as.numeric(gsub("^Y", "", tree_long$Column)) + return(tree_long) +} + +#' Match Members +#' @inheritParams readWikifamilytree +#' @return A data frame containing information about the members of the family tree. +#' @keywords internal + +matchMembers <- function(text) { + member_matches <- stringr::str_extract_all(text, "\\|\\s*([A-Za-z0-9]+)\\s*=\\s*([^|}]*)")[[1]] + member_matches <- gsub("\\[|\\]|'''", "", member_matches) # Remove formatting + + members_df <- data.frame( + identifier = stringr::str_trim(stringr::str_extract(member_matches, "^[^=]+")), + name = stringr::str_trim(stringr::str_extract(member_matches, "(?<=\\=).*")), + stringsAsFactors = FALSE + ) + + # Remove leading pipes (`|`) from identifiers for consistency + members_df$identifier <- gsub("^\\|\\s*", "", members_df$identifier) + + # remove summary row + members_df <- members_df[members_df$identifier != "summary", ] + + return(members_df) +} + +#' Extract Summary Text +#' @inheritParams readWikifamilytree +#' @return A character string containing the summary text. +#' @keywords internal +#' @export + +extractSummaryText <- function(text) { + summary_match <- stringr::str_match(text, "\\{\\{familytree/start \\|summary=(.*?)\\}\\}") + summary_text <- ifelse(!is.na(summary_match[, 2]), summary_match[, 2], NA) + return(summary_text) +} + +#' Parse Tree +#' @param tree_lines A character vector containing the lines of the tree structure. +#' @return A data frame containing the tree structure. +#' @keywords internal +#' @export + +parseTree <- function(tree_lines) { + tree_matrix <- base::strsplit(tree_lines, "\\|") # Split each row into columns + max_cols <- max(sapply(tree_matrix, length)) # Find the max column count + + # Convert to a data frame (ensures correct structure) + tree_df <- do.call(rbind, lapply(tree_matrix, function(row) { + length(row) <- max_cols # Ensure uniform column length + return(row) + })) + + tree_df <- as.data.frame(tree_df, stringsAsFactors = FALSE) + colnames(tree_df) <- paste0("Y", seq_len(ncol(tree_df))) # Assign column names + tree_df$Row <- seq_len(nrow(tree_df)) # Assign row numbers + return(tree_df) +} + +#' infer relationship from tree template +#' +#' @param tree_long A data frame containing the tree structure in long format. +#' @return A data frame containing the relationships between family members. +#' @keywords internal +#' +parseRelationships <- function(tree_long) { + relationships <- data.frame( + id = tree_long$id, + momID = NA_character_, + dadID = NA_character_, + spouseID = NA_character_, + stringsAsFactors = FALSE + ) + + # Loop through rows to find connections + for (i in seq_len(nrow(tree_long))) { + row <- tree_long[i, ] + + # **Parent-Child Detection** + if (row$Value == "y") { + parent <- tree_long$Value[tree_long$Row == row$Row - 1 & tree_long$Column == row$Column] + child <- tree_long$Value[tree_long$Row == row$Row + 1 & tree_long$Column == row$Column] + + if (length(parent) == 0) parent <- NA + if (length(child) == 0) child <- NA + # Assign mom/dad IDs based on tree structure + if (!is.na(parent) && !is.na(child)) { + relationships$momID[relationships$id == child] <- parent + relationships$dadID[relationships$id == child] <- parent # Assuming one parent detected for now + } + } + + # **Spouse Detection** + if (row$Value == "+") { + spouse1 <- tree_long$Value[tree_long$Row == row$Row & tree_long$Column == row$Column - 1] + spouse2 <- tree_long$Value[tree_long$Row == row$Row & tree_long$Column == row$Column + 1] + + if (!is.na(spouse1) && !is.na(spouse2)) { + relationships$spouseID[relationships$id == spouse1] <- spouse2 + relationships$spouseID[relationships$id == spouse2] <- spouse1 + } + } + } + + return(relationships) +} From 5000ed5e1d4746c6dffbd8341e1d7e29ef119786 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 17:02:32 -0400 Subject: [PATCH 03/11] Rename readPedigree.R to readGedcom.R --- R/{readPedigree.R => readGedcom.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename R/{readPedigree.R => readGedcom.R} (100%) diff --git a/R/readPedigree.R b/R/readGedcom.R similarity index 100% rename from R/readPedigree.R rename to R/readGedcom.R From ddac2dfa7dde5647abab3d58b09edb9cc960dbb8 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 17:13:52 -0400 Subject: [PATCH 04/11] spilting file --- R/readGedcom.R | 198 ------------------------------------------------- 1 file changed, 198 deletions(-) diff --git a/R/readGedcom.R b/R/readGedcom.R index 3faafa07..9aa8f48e 100644 --- a/R/readGedcom.R +++ b/R/readGedcom.R @@ -576,201 +576,3 @@ countPatternRows <- function(file) { return(num_rows) } -#' Read Wiki Family Tree -#' -#' @param text A character string containing the text of a family tree in wiki format. -#' @param verbose A logical value indicating whether to print messages. -#' @param file_path The path to the file containing the family tree. -#' @param ... Additional arguments (not used). -#' -#' @return A list containing the summary, members, structure, and relationships of the family tree. -#' @export -readWikifamilytree <- function(text=NULL, verbose = FALSE, file_path = NULL, ...) { - # Checks - if (is.null(text) && is.null(file_path)) { - stop("Either 'text' or 'file_path' must be provided.") - } - # read from file if provided -if (!is.null(file_path)){ - - if (!file.exists(file_path)) stop("File does not exist: ", file_path) - - if (verbose) { - print(paste("Reading file:", file_path)) - } - file <- data.frame(X1 = readLines(file_path)) - file_length <- nrow(file) - if (verbose) { - print(paste0("File is ", file_length, " lines long")) - } - text <- paste0(file$X1, collapse = "\n") -} - # Extract summary text - - summary_text <- extractSummaryText(text) - # Extract all lines defining the family tree - tree_lines <- unlist(stringr::str_extract_all(text, "\\{\\{familytree.*?\\}\\}")) - tree_lines <- tree_lines[!stringr::str_detect(tree_lines, "start|end")] # Remove start/end markers - tree_lines <- gsub("\\{\\{familytree(.*?)\\}\\}", "\\1", tree_lines) # Remove wrapping markup - - # Convert tree structure into a coordinate grid (preserves symbols!) - tree_df <- parseTree(tree_lines) - - # Identify columns that start with "Y" - cols_to_pivot <- grep("^Y", names(tree_df), value = TRUE) - - # Reshape from wide to long format - tree_long <- makeLongTree(tree_df, cols_to_pivot) - - # Extract member definitions - members_df <- matchMembers(text) - members_df$id <- paste0("P", seq_len(nrow(members_df))) # Assign unique person IDs - - # Merge names into the tree structure (keeping all symbols!) - tree_long <- merge(tree_long, members_df, by.x = "Value", by.y = "identifier", all.x = TRUE) - - tree_long$DisplayName <- ifelse(!is.na(tree_long$name), tree_long$name, tree_long$Value) # Use name if available - - # parse relationships and infer them - - relationships_df <- parseRelationships(tree_long) - - # relationships_df <- processParents(tree_long, datasource = "wiki") - - - - # Return structured table of the family tree (symbols included) - list( - summary = summary_text, - members = members_df, - structure = tree_long, - relationships = relationships_df - ) -} - -#' Make Long Tree -#' @param tree_df A data frame containing the tree structure. -#' @param cols_to_pivot A character vector of column names to pivot. -#' @return A long data frame containing the tree structure. -#' @keywords internal -makeLongTree <- function(tree_df, cols_to_pivot) { - tree_long <- stats::reshape(tree_df, - varying = cols_to_pivot, - v.names = "Value", - timevar = "Column", - times = cols_to_pivot, - idvar = setdiff(names(tree_df), cols_to_pivot), - direction = "long" - ) - - tree_long <- tree_long[!is.na(tree_long$Value), ] - tree_long$Value <- stringr::str_trim(tree_long$Value) - tree_long$Column <- as.numeric(gsub("^Y", "", tree_long$Column)) - return(tree_long) -} - -#' Match Members -#' @inheritParams readWikifamilytree -#' @return A data frame containing information about the members of the family tree. -#' @keywords internal - -matchMembers <- function(text) { - member_matches <- stringr::str_extract_all(text, "\\|\\s*([A-Za-z0-9]+)\\s*=\\s*([^|}]*)")[[1]] - member_matches <- gsub("\\[|\\]|'''", "", member_matches) # Remove formatting - - members_df <- data.frame( - identifier = stringr::str_trim(stringr::str_extract(member_matches, "^[^=]+")), - name = stringr::str_trim(stringr::str_extract(member_matches, "(?<=\\=).*")), - stringsAsFactors = FALSE - ) - - # Remove leading pipes (`|`) from identifiers for consistency - members_df$identifier <- gsub("^\\|\\s*", "", members_df$identifier) - - # remove summary row - members_df <- members_df[members_df$identifier != "summary", ] - - return(members_df) -} - -#' Extract Summary Text -#' @inheritParams readWikifamilytree -#' @return A character string containing the summary text. -#' @keywords internal -#' @export - -extractSummaryText <- function(text) { - summary_match <- stringr::str_match(text, "\\{\\{familytree/start \\|summary=(.*?)\\}\\}") - summary_text <- ifelse(!is.na(summary_match[, 2]), summary_match[, 2], NA) - return(summary_text) -} - -#' Parse Tree -#' @param tree_lines A character vector containing the lines of the tree structure. -#' @return A data frame containing the tree structure. -#' @keywords internal -#' @export - -parseTree <- function(tree_lines) { - tree_matrix <- base::strsplit(tree_lines, "\\|") # Split each row into columns - max_cols <- max(sapply(tree_matrix, length)) # Find the max column count - - # Convert to a data frame (ensures correct structure) - tree_df <- do.call(rbind, lapply(tree_matrix, function(row) { - length(row) <- max_cols # Ensure uniform column length - return(row) - })) - - tree_df <- as.data.frame(tree_df, stringsAsFactors = FALSE) - colnames(tree_df) <- paste0("Y", seq_len(ncol(tree_df))) # Assign column names - tree_df$Row <- seq_len(nrow(tree_df)) # Assign row numbers - return(tree_df) -} - -#' infer relationship from tree template -#' -#' @param tree_long A data frame containing the tree structure in long format. -#' @return A data frame containing the relationships between family members. -#' @keywords internal -#' -parseRelationships <- function(tree_long) { - relationships <- data.frame( - id = tree_long$id, - momID = NA_character_, - dadID = NA_character_, - spouseID = NA_character_, - stringsAsFactors = FALSE - ) - - # Loop through rows to find connections - for (i in seq_len(nrow(tree_long))) { - row <- tree_long[i, ] - - # **Parent-Child Detection** - if (row$Value == "y") { - parent <- tree_long$Value[tree_long$Row == row$Row - 1 & tree_long$Column == row$Column] - child <- tree_long$Value[tree_long$Row == row$Row + 1 & tree_long$Column == row$Column] - - if (length(parent) == 0) parent <- NA - if (length(child) == 0) child <- NA - # Assign mom/dad IDs based on tree structure - if (!is.na(parent) && !is.na(child)) { - relationships$momID[relationships$id == child] <- parent - relationships$dadID[relationships$id == child] <- parent # Assuming one parent detected for now - } - } - - # **Spouse Detection** - if (row$Value == "+") { - spouse1 <- tree_long$Value[tree_long$Row == row$Row & tree_long$Column == row$Column - 1] - spouse2 <- tree_long$Value[tree_long$Row == row$Row & tree_long$Column == row$Column + 1] - - if (!is.na(spouse1) && !is.na(spouse2)) { - relationships$spouseID[relationships$id == spouse1] <- spouse2 - relationships$spouseID[relationships$id == spouse2] <- spouse1 - } - } - } - - return(relationships) -} From 48db447507ee6380c7af6802c3d48813e406cf03 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 18:38:40 -0400 Subject: [PATCH 05/11] refactoring --- R/readGedcom.R | 259 +++++++++++++--------------- data/royal92.rda | Bin 68816 -> 69068 bytes tests/testthat/test-readPedigrees.R | 21 ++- 3 files changed, 135 insertions(+), 145 deletions(-) diff --git a/R/readGedcom.R b/R/readGedcom.R index 9aa8f48e..1fd039ed 100644 --- a/R/readGedcom.R +++ b/R/readGedcom.R @@ -120,38 +120,34 @@ readGedcom <- function(file_path, next } # PERSONAL_NAME_PIECES := NAME | NPFX | GIVN | NICK | SPFX | SURN | NSFX - if (num_rows$num_givn_rows > 0 && grepl(" GIVN", tmpv)) { - vars$name_given_pieces <- extract_info(tmpv, "GIVN") - next - } + result <- process_tag("GIVN", "name_given_pieces", num_rows, tmpv, vars) + vars <- result$vars + if (result$matched) next # npfx := Name Prefix - if (num_rows$num_npfx_rows > 0 && grepl(" NPFX", tmpv)) { - vars$name_npfx <- extract_info(tmpv, "NPFX") - next - } + result <- process_tag("NPFX", "name_npfx", num_rows, tmpv, vars) + vars <- result$vars + if (result$matched) next # NICK := Nickname - if (num_rows$num_nick_rows > 0 && grepl(" NICK", tmpv)) { - vars$name_nick <- extract_info(tmpv, "NICK") - next - } + result <- process_tag("NICK", "name_nick", num_rows, tmpv, vars) + vars <- result$vars + if (result$matched) next # surn := Surname - if (num_rows$num_surn_rows > 0 && grepl(" SURN", tmpv)) { - vars$name_surn_pieces <- extract_info(tmpv, "SURN") - next - } + result <- process_tag("SURN", "name_surn_pieces", num_rows, tmpv, vars) + vars <- result$vars + if (result$matched) next # nsfx := Name suffix - if (num_rows$num_nsfx_rows > 0 && grepl(" NSFX", tmpv)) { - vars$name_nsfx <- extract_info(tmpv, "NSFX") - next - } - if (num_rows$num_marnm_rows > 0 && grepl(" _MARNM", tmpv)) { - vars$name_marriedsurn <- extract_info(tmpv, "_MARNM") - next - } + result <- process_tag("NSFX", "name_nsfx", num_rows, tmpv, vars) + vars <- result$vars + if (result$matched) next + + result <- process_tag("_MARNM", "name_marriedsurn", num_rows, tmpv, vars) + vars <- result$vars + if (result$matched) next + # Birth event related information if (num_rows$num_birt_rows > 0 && grepl(" BIRT", tmpv)) { if (num_rows$num_date_rows > 0 && i + 1 <= file_length) { @@ -190,118 +186,85 @@ readGedcom <- function(file_path, next } - if (grepl(" SEX", tmpv)) { - vars$sex <- extract_info(tmpv, "SEX") - next - } - # Individual Attributes + # Attribute tags using process_tag() + for (tag_field in list( + c("SEX", "sex"), - # CAST caste - # g7:CAST The name of an individual’s rank or status in society which is sometimes based on racial or religious differences, or differences in wealth, inherited rank, profession, or occupation. - if (num_rows$num_cast_rows > 0 && grepl(" CAST", tmpv)) { - vars$attribute_caste <- extract_info(tmpv, "CAST") - next - } - # DSCR physical description - # g7:DSCR The physical characteristics of a person. - if (num_rows$num_dscr_rows > 0 && grepl(" DSCR", tmpv)) { - vars$attribute_description <- extract_info(tmpv, "DSCR") - next - } - # EDUC education - # g7:EDUC Indicator of a level of education attained. - if (num_rows$num_educ_rows > 0 && grepl(" EDUC", tmpv)) { - vars$attribute_education <- extract_info(tmpv, "EDUC") - next - } - # IDNO identifying number - # g7:IDNO A number or other string assigned to identify a person within some significant external system. It must have a TYPE substructure to define what kind of identification number is being provided. - if (num_rows$num_idno_rows > 0 && grepl(" IDNO", tmpv)) { - vars$attribute_idnumber <- extract_info(tmpv, "IDNO") - next - } - # NATI nationality - # g7:NATI An individual’s national heritage or origin, or other folk, house, kindred, lineage, or tribal interest. - if (num_rows$num_nati_rows > 0 && grepl(" NATI", tmpv)) { - vars$attribute_nationality <- extract_info(tmpv, "NATI") - next - } - # NCHI number of children - # g7:INDI-NCHI The number of children that this person is known to be the parent of (all marriages). - if (num_rows$num_nchi_rows > 0 && grepl(" NCHI", tmpv)) { - vars$attribute_children <- extract_info(tmpv, "NCHI") - next - } + # CAST caste + # g7:CAST The name of an individual’s rank or status in society which is sometimes based on racial or religious differences, or differences in wealth, inherited rank, profession, or occupation. + c("CAST", "attribute_caste"), - # NMR number of marriages - # g7:NMR The number of times this person has participated in a family as a spouse or parent. - if (num_rows$num_nmr_rows > 0 && grepl(" NMR", tmpv)) { - vars$attribute_marriages <- extract_info(tmpv, "NMR") - next - } + # DSCR physical description + # g7:DSCR The physical characteristics of a person. + c("DSCR", "attribute_description"), - # OCCU occupation - # g7:OCCU The type of work or profession of an individual. - if (num_rows$num_occu_rows > 0 && grepl(" OCCU", tmpv)) { - vars$attribute_occupation <- extract_info(tmpv, "OCCU") - next - } - # PROP property - # g7:PROP Pertaining to possessions such as real estate or other property of interest. + # EDUC education + # g7:EDUC Indicator of a level of education attained. + c("EDUC", "attribute_education"), - if (num_rows$num_prop_rows > 0 && grepl(" PROP", tmpv)) { - vars$attribute_property <- extract_info(tmpv, "PROP") - next - } + # IDNO identifying number + # g7:IDNO A number or other string assigned to identify a person within some significant external system. It must have a TYPE substructure to define what kind of identification number is being provided. + c("IDNO", "attribute_idnumber"), - # RELI religion - # g7:INDI-RELI A religious denomination to which a person is affiliated or for which a record applies. - if (num_rows$num_reli_rows > 0 && grepl(" RELI", tmpv)) { - vars$attribute_religion <- extract_info(tmpv, "RELI") - next - } - # RESI residence - # g7:INDI-RESI An address or place of residence where an individual resided. - if (num_rows$num_resi_rows > 0 && grepl(" RESI", tmpv)) { - vars$attribute_residence <- extract_info(tmpv, "RESI") - next - } + # NATI nationality + # g7:NATI An individual’s national heritage or origin, or other folk, house, kindred, lineage, or tribal interest. + c("NATI", "attribute_nationality"), - # SSN social security number - # g7:SSN A number assigned by the United States Social Security Administration, used for tax identification purposes. It is a type of IDNO. - if (num_rows$num_ssn_rows > 0 && grepl(" SSN", tmpv)) { - vars$attribute_ssn <- extract_info(tmpv, "SSN") - next - } - # TITL title - # g7:INDI-TITL A formal designation used by an individual in connection with positions of royalty or other social status, such as Grand Duke. - if (num_rows$num_titl_rows > 0 && grepl(" TITL", tmpv)) { - vars$attribute_title <- extract_info(tmpv, "TITL") - next + # NCHI number of children + # g7:INDI-NCHI The number of children that this person is known to be the parent of (all marriages). + c("NCHI", "attribute_children"), + + # NMR number of marriages + # g7:NMR The number of times this person has participated in a family as a spouse or parent. + c("NMR", "attribute_marriages"), + + # OCCU occupation + # g7:OCCU The type of work or profession of an individual. + c("OCCU", "attribute_occupation"), + + # PROP property + # g7:PROP Pertaining to possessions such as real estate or other property of interest. + c("PROP", "attribute_property"), + + # RELI religion + # g7:INDI-RELI A religious denomination to which a person is affiliated or for which a record applies. + c("RELI", "attribute_religion"), + + # RESI residence + # g7:INDI-RESI An address or place of residence where an individual resided. + c("RESI", "attribute_residence"), + + # SSN social security number + # g7:SSN A number assigned by the United States Social Security Administration, used for tax identification purposes. It is a type of IDNO. + c("SSN", "attribute_ssn"), + + # TITL title + # g7:INDI-TITL A formal designation used by an individual in connection with positions of royalty or other social status, such as Grand Duke. + c("TITL", "attribute_title") + )) { + result <- process_tag(tag_field[1], tag_field[2], num_rows, tmpv, vars) + vars <- result$vars + if (result$matched) next } # relationship data # g7:INDI-FAMC ## The family in which an individual appears as a child. It is also used with a g7:FAMC-STAT substructure to show individuals who are not children of the family. See FAMILY_RECORD for more details. - if (num_rows$num_famc_rows > 0 && grepl(" FAMC", tmpv)) { - if (is.na(vars$FAMC)) { - vars$FAMC <- stringr::str_extract(tmpv, "(?<=@.)\\d*(?=@)") - } else { - vars$FAMC <- paste0(vars$FAMC, ", ", stringr::str_extract(tmpv, "(?<=@.)\\d*(?=@)")) - } - next - } +result <- process_tag("FAMC", "FAMC", num_rows, tmpv, vars, + extractor = function(x) stringr::str_extract(x, "(?<=@.)\\d*(?=@)"), + mode = "append") + vars <- result$vars + if (result$matched) next + # FAMS (Family spouse) g7:FAMS # The family in which an individual appears as a partner. See FAMILY_RECORD for more details. - if (num_rows$num_fams_rows > 0 && grepl(" FAMS", tmpv)) { - if (is.na(vars$FAMS)) { - vars$FAMS <- stringr::str_extract(tmpv, "(?<=@.)\\d*(?=@)") - } else { - vars$FAMS <- paste0(vars$FAMS, ", ", stringr::str_extract(tmpv, "(?<=@.)\\d*(?=@)")) - } - next - } + result <- process_tag("FAMS", "FAMS", num_rows, tmpv, vars, + extractor = function(x) stringr::str_extract(x, "(?<=@.)\\d*(?=@)"), + mode = "append") + vars <- result$vars + if (result$matched) next + if (verbose && i %% 1000 == 0) { cat("Processed", i, "lines\n") } @@ -383,8 +346,7 @@ readGedcom <- function(file_path, #' @param df_temp A data frame containing information about individuals. #' @return A list mapping family IDs to parent IDs. #' @keywords internal -createFamilyToParentsMapping <- function(df_temp, datasource) { - if (datasource == "gedcom") { +mapFAMS2parents <- function(df_temp) { if (!all(c("FAMS", "sex") %in% colnames(df_temp))) { warning("The data frame does not contain the necessary columns (FAMS, sex)") return(NULL) @@ -411,10 +373,6 @@ createFamilyToParentsMapping <- function(df_temp, datasource) { } } } - } else if (datasource == "wiki") { - message("The data source is not supported") - return(df_temp) - } return(family_to_parents) } @@ -425,13 +383,11 @@ createFamilyToParentsMapping <- function(df_temp, datasource) { #' #' @param df_temp A data frame containing individual information. #' @param family_to_parents A list mapping family IDs to parent IDs. -#' @param datasource A string indicating the data source. Options are "gedcom" and "wiki". #' @return A data frame with added momID and dad_ID columns. #' @keywords internal -assignParentIDs <- function(df_temp, family_to_parents, datasource) { +mapFAMC2parents <- function(df_temp, family_to_parents) { df_temp$momID <- NA_character_ df_temp$dadID <- NA_character_ - if (datasource == "gedcom") { for (i in 1:nrow(df_temp)) { if (!is.na(df_temp$FAMC[i])) { famc_ids <- unlist(strsplit(df_temp$FAMC[i], ", ")) @@ -448,10 +404,6 @@ assignParentIDs <- function(df_temp, family_to_parents, datasource) { } } return(df_temp) - } else if (datasource == "wiki") { - message("No parents information available for wiki data") - return(df_temp) - } } #' Process parents information @@ -477,11 +429,11 @@ processParents <- function(df_temp, datasource) { return(df_temp) } - family_to_parents <- createFamilyToParentsMapping(df_temp, datasource = datasource) + family_to_parents <- mapFAMS2parents(df_temp) if (is.null(family_to_parents) || length(family_to_parents) == 0) { return(df_temp) } - df_temp <- assignParentIDs(df_temp, family_to_parents, datasource = datasource) + df_temp <- mapFAMC2parents(df_temp, family_to_parents) return(df_temp) } @@ -576,3 +528,36 @@ countPatternRows <- function(file) { return(num_rows) } +#' Process a GEDCOM Tag +#' +#' Extracts and assigns a value to a specified field in `vars` if the pattern is present. +#' Returns both the updated variable list and a flag indicating whether the tag was matched. +#' +#' @param tag The GEDCOM tag (e.g., "SEX", "CAST", etc.). +#' @param field_name The name of the variable to assign to in `vars`. +#' @param pattern_rows Output from `countPatternRows()`. +#' @param line The GEDCOM line to parse. +#' @param vars The current list of variables to update. +#' @return A list with updated `vars` and a `matched` flag. +#' @keywords internal +process_tag <- function(tag, field_name, pattern_rows, line, vars, + extractor = NULL, mode = "replace") { + count_name <- paste0("num_", tolower(tag), "_rows") + matched <- FALSE + if (!is.null(pattern_rows[[count_name]]) && + pattern_rows[[count_name]] > 0 && + grepl(paste0(" ", tag), line)) { + + value <- if (is.null(extractor)) extract_info(line, tag) else extractor(line) + + if (mode == "append" && !is.na(vars[[field_name]])) { + vars[[field_name]] <- paste0(vars[[field_name]], ", ", value) + } else { + vars[[field_name]] <- value + } + + matched <- TRUE + } + return(list(vars = vars, matched = matched)) +} + diff --git a/data/royal92.rda b/data/royal92.rda index 357f1aba690c24fe19fb45b49b1111b5a999cbfd..4a10b174c582e692825f6afe8e14ff1b069d9978 100644 GIT binary patch literal 69068 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�whZ)U9~#^Qth6x!)Y}^VQz(z`The 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 literal 68816 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şna2eB>} zMe!Y};Nv>7__cQ2^F@Wc4SzmYCRcvS<+Q)%w0EpESo1g&=B>9LS!#5gM|>S94H#C0$X{2}>*?qJbJc?mz3 zV`f-pFkFr*Z>H}=J!lJ|SP_`loe%=us-3KV*99;I{)pSsuogdK`!qo!Ft#j-OT3X+ z3rBH#l;ec2-Z>esWU#2p{u|7oJYCD42oQV;DEOL(5))bewf?uBeZPz2OEl~_fD*>5hhc0~2|%Ouesv_f@y39-_vb;cny2WItl*%9I}oHRKJ=8#@<}VWjmE)G zDVy&e4;D#Ov`7|W(KJ_WsLi_h9V!oo^v(UvzCXeV1XL>wOjnB&O5NY0^QDO0RNAz= z+;)miEW#M(wsFA36zwm=7fGa^WJ>pI)cm`N)yS;n31H>bJ+NG6F;>t44b)Dq8ye5I zey11zb*@zh*P79{NQJ@%Pp&&{bj@`=5&-)n+s?YNsi=hSv(7~x{I;-K>HD0j)~Hpn zvj4o)liUD`hIre!JkKSRq!{zVt$qknRho-cU{FcE*blve6fE}%pcQW~?Av*SdFois z%dAwUxTFsobB6Fma;+wYi%U;VZGBWqDAfKH+CS36(s`tX31-$MO~CywG1dYGNwKSC zgN9Bs8vLUBg1Dpp$eM9AanFZ(1s_bh(oFNlfZ(&p3*|WC3j##|+b^vLs z3-IeT9a$3Vs=)ilK5eb-v9+RZ$yBc_h=Wi!yhWO>{~cuiH)@(z8%Zb>`AfqeAlk89 z*^y(OeW^Xl0GwFt(r;8y4luuY!Yf|=%Bk~ILv9fdrR@%9r9*-78mU{Kd zCg&gBhV}k&PMJdc+XRYKPzLYRRt6YwfZ>oV}l3+~&nQ|cJ#aH3kqy9M=4b>&+ z`bfH{$pw^Ly*w?)WQLh{r z^0R!bw~`}1fASupWCcjJVF*v@SL_f+13!U_!8L+U$qET#fC$@_C!)0U`60d8YL3W3 zBbP+)Qi3q*%uSmz&aC{#_o+c6%nwqSIZOo=ks~ahPKI23jH1H5fN8g20TlIyk+3rI zL!e-V{p3CCotSPi{T(&O<`tAmKJvAN&HG)(vXc`9JJ{I8*m*w6P08<3p}+D%6>tOs zHg5i^GoG9tA^T6PFm3*v&&2J*2GQe!i%l|*U26~o_4g;=$R*R?rRcIeJ*2O~lWa{3 zCYe!FKAZJ4Ov~A&K%|=;iS~>D40psQ-T{m9w|yLH6D&hhz(~kW@E4~Z_zuD^ zX;op62Fo-T6g}xE)v-fsiYfWsyUS=7-sli>WL3T zeT#(qE(`p`)YZk0d_qj@rBi^PKt?ecJupS{4)^u`L~pk3FqDR9>^vI=WH@U#q@xvm z7tb>rOt5IA8EzjARoXW13V6&fG$+tEbvE#Mx|yK$kP%4NVl~2JXkxqwDI|xErrt~j z=k+#4>=Yvsr5_pTG|+8|Y2B5GDvQ2=tv}8sx}@Ns$j~1(S8P3iil^L0)BCwiBCe?> z-T@txz1vSF0;_xnrHM5B3mAM9Lco9)`Vv)b#gb$`F&Q_&joDrilAU{w%?D-8XYg3( ziuk@7kd$(Gp_Hs~(lTr^!`{*GqJmjeUVGJ$<+i^8=$nRtUDL5>yLgJ*c0=4VqdGwc zIhiB4l^9?iT1RU*6^I9;zk~KemFk3sPS_(c8Ft=N%b!U41N^TOM4RnuEEulDOR{M) z-gmnP$pxOYZXnrnm_H)0&&&QDdh}s-P*FHWK)+fDPS-YHA70%b!Wd;Yfs&MDNs?FO z&%%%bi2u{g60{J3>>OzJGzPQ=&OrVhX!fuIE-X^o`8WH~uZ?3N#W30I~^ZkKwv0Xh(5+ zOG!G4CV*Txmwsv7rQjtBeLAcAJYWscc;MM>8%Vnq4pu z`u%GCxbr2)lq;rtKx|jl+1N@PMuNuz)D>fm`a+O4)MjYj?*cY0LGCr7wdvLi40l8x y>hQ=aD%~i}M|^ViR$$li00009kr;>o0jik Date: Mon, 7 Apr 2025 18:39:13 -0400 Subject: [PATCH 06/11] docs --- man/combine_columns.Rd | 2 +- man/countPatternRows.Rd | 2 +- man/extractSummaryText.Rd | 2 +- man/extract_info.Rd | 2 +- man/makeLongTree.Rd | 2 +- ...{assignParentIDs.Rd => mapFAMC2parents.Rd} | 10 +- ...ToParentsMapping.Rd => mapFAMS2parents.Rd} | 8 +- man/matchMembers.Rd | 2 +- man/parseRelationships.Rd | 2 +- man/parseTree.Rd | 2 +- man/processParents.Rd | 2 +- man/process_tag.Rd | 35 ++++ man/readGedcom.Rd | 2 +- man/readWikifamilytree.Rd | 13 +- vignettes/ASOIAF.html | 170 +++++++++++------- 15 files changed, 165 insertions(+), 91 deletions(-) rename man/{assignParentIDs.Rd => mapFAMC2parents.Rd} (66%) rename man/{createFamilyToParentsMapping.Rd => mapFAMS2parents.Rd} (67%) create mode 100644 man/process_tag.Rd diff --git a/man/combine_columns.Rd b/man/combine_columns.Rd index 86e6d4f9..43554b0b 100644 --- a/man/combine_columns.Rd +++ b/man/combine_columns.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readGedcom.R \name{combine_columns} \alias{combine_columns} \title{Combine Columns} diff --git a/man/countPatternRows.Rd b/man/countPatternRows.Rd index edcdda52..f9100912 100644 --- a/man/countPatternRows.Rd +++ b/man/countPatternRows.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readGedcom.R \name{countPatternRows} \alias{countPatternRows} \title{Check for Pattern Rows} diff --git a/man/extractSummaryText.Rd b/man/extractSummaryText.Rd index ad1e90a9..9b12be26 100644 --- a/man/extractSummaryText.Rd +++ b/man/extractSummaryText.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readWikifamilytree.R \name{extractSummaryText} \alias{extractSummaryText} \title{Extract Summary Text} diff --git a/man/extract_info.Rd b/man/extract_info.Rd index 0001eb99..3f5bbc1d 100644 --- a/man/extract_info.Rd +++ b/man/extract_info.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readGedcom.R \name{extract_info} \alias{extract_info} \title{Extract Information from Line} diff --git a/man/makeLongTree.Rd b/man/makeLongTree.Rd index e03db2f9..96d4a514 100644 --- a/man/makeLongTree.Rd +++ b/man/makeLongTree.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readWikifamilytree.R \name{makeLongTree} \alias{makeLongTree} \title{Make Long Tree} diff --git a/man/assignParentIDs.Rd b/man/mapFAMC2parents.Rd similarity index 66% rename from man/assignParentIDs.Rd rename to man/mapFAMC2parents.Rd index 8bd15170..3ebd6dd7 100644 --- a/man/assignParentIDs.Rd +++ b/man/mapFAMC2parents.Rd @@ -1,17 +1,15 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R -\name{assignParentIDs} -\alias{assignParentIDs} +% Please edit documentation in R/readGedcom.R +\name{mapFAMC2parents} +\alias{mapFAMC2parents} \title{Assign momID and dadID based on family mapping} \usage{ -assignParentIDs(df_temp, family_to_parents, datasource) +mapFAMC2parents(df_temp, family_to_parents) } \arguments{ \item{df_temp}{A data frame containing individual information.} \item{family_to_parents}{A list mapping family IDs to parent IDs.} - -\item{datasource}{A string indicating the data source. Options are "gedcom" and "wiki".} } \value{ A data frame with added momID and dad_ID columns. diff --git a/man/createFamilyToParentsMapping.Rd b/man/mapFAMS2parents.Rd similarity index 67% rename from man/createFamilyToParentsMapping.Rd rename to man/mapFAMS2parents.Rd index 20d8216d..25d5a9f3 100644 --- a/man/createFamilyToParentsMapping.Rd +++ b/man/mapFAMS2parents.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R -\name{createFamilyToParentsMapping} -\alias{createFamilyToParentsMapping} +% Please edit documentation in R/readGedcom.R +\name{mapFAMS2parents} +\alias{mapFAMS2parents} \title{Create a mapping of family IDs to parent IDs} \usage{ -createFamilyToParentsMapping(df_temp, datasource) +mapFAMS2parents(df_temp) } \arguments{ \item{df_temp}{A data frame containing information about individuals.} diff --git a/man/matchMembers.Rd b/man/matchMembers.Rd index e02311af..382c05ec 100644 --- a/man/matchMembers.Rd +++ b/man/matchMembers.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readWikifamilytree.R \name{matchMembers} \alias{matchMembers} \title{Match Members} diff --git a/man/parseRelationships.Rd b/man/parseRelationships.Rd index a02a5983..24e864b5 100644 --- a/man/parseRelationships.Rd +++ b/man/parseRelationships.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readWikifamilytree.R \name{parseRelationships} \alias{parseRelationships} \title{infer relationship from tree template} diff --git a/man/parseTree.Rd b/man/parseTree.Rd index e429662c..6982013e 100644 --- a/man/parseTree.Rd +++ b/man/parseTree.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readWikifamilytree.R \name{parseTree} \alias{parseTree} \title{Parse Tree} diff --git a/man/processParents.Rd b/man/processParents.Rd index e8077472..9aa205a7 100644 --- a/man/processParents.Rd +++ b/man/processParents.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readGedcom.R \name{processParents} \alias{processParents} \title{Process parents information} diff --git a/man/process_tag.Rd b/man/process_tag.Rd new file mode 100644 index 00000000..14dfa65e --- /dev/null +++ b/man/process_tag.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/readGedcom.R +\name{process_tag} +\alias{process_tag} +\title{Process a GEDCOM Tag} +\usage{ +process_tag( + tag, + field_name, + pattern_rows, + line, + vars, + extractor = NULL, + mode = "replace" +) +} +\arguments{ +\item{tag}{The GEDCOM tag (e.g., "SEX", "CAST", etc.).} + +\item{field_name}{The name of the variable to assign to in `vars`.} + +\item{pattern_rows}{Output from `countPatternRows()`.} + +\item{line}{The GEDCOM line to parse.} + +\item{vars}{The current list of variables to update.} +} +\value{ +A list with updated `vars` and a `matched` flag. +} +\description{ +Extracts and assigns a value to a specified field in `vars` if the pattern is present. +Returns both the updated variable list and a flag indicating whether the tag was matched. +} +\keyword{internal} diff --git a/man/readGedcom.Rd b/man/readGedcom.Rd index a5136bc5..09d19040 100644 --- a/man/readGedcom.Rd +++ b/man/readGedcom.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readGedcom.R \name{readGedcom} \alias{readGedcom} \title{Read a GEDCOM File} diff --git a/man/readWikifamilytree.Rd b/man/readWikifamilytree.Rd index b06acf7b..58a64efd 100644 --- a/man/readWikifamilytree.Rd +++ b/man/readWikifamilytree.Rd @@ -1,13 +1,22 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/readPedigree.R +% Please edit documentation in R/readWikifamilytree.R \name{readWikifamilytree} \alias{readWikifamilytree} \title{Read Wiki Family Tree} \usage{ -readWikifamilytree(text) +readWikifamilytree(text = NULL, verbose = FALSE, file_path = NULL, ...) } \arguments{ \item{text}{A character string containing the text of a family tree in wiki format.} + +\item{verbose}{A logical value indicating whether to print messages.} + +\item{file_path}{The path to the file containing the family tree.} + +\item{...}{Additional arguments (not used).} +} +\value{ +A list containing the summary, members, structure, and relationships of the family tree. } \description{ Read Wiki Family Tree diff --git a/vignettes/ASOIAF.html b/vignettes/ASOIAF.html index 9b925ee3..7e1f9f05 100644 --- a/vignettes/ASOIAF.html +++ b/vignettes/ASOIAF.html @@ -360,13 +360,13 @@

Load Packages and Data

structure of the built-in ASOIAF pedigree.

-
## ── Attaching core tidyverse packages ───── tidyverse 2.0.0 ──
+
## ── Attaching core tidyverse packages ─────────────────
 ## ✔ dplyr     1.1.4     ✔ readr     2.1.5
 ## ✔ forcats   1.0.0     ✔ stringr   1.5.1
 ## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
 ## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
 ## ✔ purrr     1.0.4     
-## ── Conflicts ─────────────────────── tidyverse_conflicts() ──
+## ── Conflicts ──────────────── tidyverse_conflicts() ──
 ## ✖ dplyr::between()     masks BGmisc::between()
 ## ✖ dplyr::filter()      masks stats::filter()
 ## ✖ dplyr::first()       masks BGmisc::first()
@@ -400,7 +400,7 @@ 

Load Packages and Data

Prepare and Validate Sex Codes

-

Many pedigree-based algorithms rely on valid sex codes for downstream +

Many pedigree-based algorithms rely on biological sex for downstream calculationss and visualization. We use checkSex() to inspect the sex variable, repairing inconsistencies programmatically.

@@ -408,93 +408,125 @@

Prepare and Validate Sex Codes

code_male = 1, code_female = 0, verbose = FALSE, repair = TRUE -)
+)

Compute Relatedness Matrices

-

We now compute the additive genetic relatedness matrix (add) and the -common nuclear relatedness matrix (cn) from the pedigree using ped2com() -and ped2cn(), respectively. The isChild_method argument -specifies how to identify child-parent relationships. We use -“partialparent” to account for missing parent information. The -adjacency_method argument specifies how to construct the -adjacency matrix. We use “direct” for the additive matrix and “indexed” -for the common nuclear matrix. The direct method is much faster. The -sparse argument is set to FALSE to return dense -matrices.

+

With validated pedigree data, we can now compute two distinct +relationship matrices:

+
    +
  • Additive genetic relatedness (add): Proportion of shared additive +genetic variance between individuals.

  • +
  • Common nuclear relatedness (cn): Indicates shared full-sibling +(nuclear family) environments.

  • +
+

These are derived using ped2add() and ped2cn(), respectively. Both +functions rely on internal graph traversal and adjacency structures. In +this case:

+
    +
  • We specify isChild_method = “partialparent” to allow inclusion of +dyads where one parent is unknown.

  • +
  • We choose adjacency_method = “direct” for the additive matrix to +optimize for computational speed.

  • +
  • For the common nuclear matrix, we use adjacency_method = +“indexed”, which is slower but necessary for resolving sibling-group +structures.

  • +
  • We set sparse = FALSE to return full (dense) +matrices rather than compressed sparse formats.

  • +
add <- ped2com(df_got,
   isChild_method = "partialparent",
   component = "additive",
   adjacency_method = "direct",
-  sparse = FALSE
+  sparse = TRUE
 )
 
-cn <- ped2cn(df_got,
+mt <- ped2com(df_got,
   isChild_method = "partialparent",
-  adjacency_method = "indexed",
-  sparse = FALSE
-)
+ component = "mitochondrial", + adjacency_method = "direct", + sparse = TRUE +) + +cn <- ped2cn(df_got, + isChild_method = "partialparent", + adjacency_method = "indexed", + sparse = TRUE +)

Convert to Pairwise Format

-

We convert the component matrices into a long-format table of -pairwise relationships using com2links(). This gives us a -long dataframe where each row represents a pair of individuals and their -relatedness. The function can return the entire matrix or just the lower -triangular part, which is often sufficient for our purposes. We set -writetodisk = FALSE to keep the data in memory.

+

For interpretability, we convert these square matrices into +long-format tables using com2links(). This function returns +a dataframe where each row represents a unique pair of individuals, +including their additive and common nuclear coefficients.

df_links <- com2links(
   writetodisk = FALSE,
-  ad_ped_matrix = add, cn_ped_matrix = cn,
+  ad_ped_matrix = add, cn_ped_matrix = cn, mit_ped_matrix= mt,
   drop_upper_triangular = TRUE
-)# %>%
-#  filter(ID1 != ID2)
+)# %>%
+
## 'as(<dsCMatrix>, "dgCMatrix")' is deprecated.
+## Use 'as(., "generalMatrix")' instead.
+## See help("Deprecated") and help("Matrix-deprecated").
+
#  filter(ID1 != ID2)
+

The function can return the entire matrix or just the lower +triangular part, which is often sufficient for our purposes. Setting +drop_upper_triangular = TRUE ensures we only retain one +entry per dyad, since the matrices are symmetric. We also keep the data +in memory by setting writetodisk = FALSE.

Locate Jon and Daenerys

-

Next, we extract the IDs corresponding to Jon Snow and Daenerys -Targaryen. We use the filter() function to find the rows in -the df_links dataframe where either ID1 or ID2 corresponds -to Jon Snow, and then filter again to find the row where the other ID -corresponds to Daenerys Targaryen.

-
# Find the IDs of Jon Snow and Daenerys Targaryen
-
-jon_id <- df_got %>%
-  filter(name == "Jon Snow") %>%
-  pull(ID)
-
-dany_id <- df_got %>%
-  filter(name == "Daenerys Targaryen") %>%
-  pull(ID)
-

We then filter the pairwise table to retrieve the row containing -their relationship.

-
jon_dany_row <- df_links %>%
-  filter(ID1 == jon_id | ID2 == jon_id) %>%
-  filter(ID1 %in% dany_id| ID2 %in% dany_id)
-
-jon_dany_row 
-
##   ID1 ID2     addRel cnuRel
-## 1 206 211 0.31274414      0
-## 2 211 304 0.01953125      0
-

This row contains the additive relatedness coefficient between Jon -and Daenerys, which allows us to assess how closely related they are -genetically. We’d expect to see a value of 0.25 for an Aunt-Nephew -relationship, which is what Jon and Daenerys are in the show. However, -the value is 0.3127441, indicating a more complex relationship.

+

We next identify the rows in the pairwise relatedness table that +correspond to Jon Snow and Daenerys Targaryen. First, we retrieve their +individual IDs:

+
# Find the IDs of Jon Snow and Daenerys Targaryen
+
+jon_id <- df_got %>%
+  filter(name == "Jon Snow") %>%
+  pull(ID)
+
+dany_id <- df_got %>%
+  filter(name == "Daenerys Targaryen") %>%
+  pull(ID)
+

Then we isolate their dyad:

+
jon_dany_row <- df_links %>%
+  filter(ID1 == jon_id | ID2 == jon_id) %>%
+  filter(ID1 %in% dany_id| ID2 %in% dany_id)
+
+jon_dany_row 
+
##   ID1 ID2     addRel mitRel cnuRel
+## 1 206 211 0.31274414      0      0
+## 2 211 304 0.01953125      0      0
+

This table contains the additive and nuclear relatedness estimates +for Jon and Daenerys. If the pedigree reflects their canonical +aunt-nephew relationship and is free from inbreeding, we’d expect to see +an additive coefficient close to 0.25. However, the value is 0.3127441, +indicating a more complex relationship.

-

Plotting the Pedigree with incomplete parental information

+

Plotting the Pedigree with Incomplete Parental Information

+

Many real-world and fictional pedigrees contain individuals with +unknown or partially known parentage. In such cases, plotting tools +typically fail unless these gaps are handled. We use +checkParentIDs() to:

+
    +
  • Identify individuals with one known parent and one +missing

  • +
  • Create “phantom” placeholders for the missing parent

  • +
+

-Optionally repair and harmonize parent fields

To facilitate plotting, we check for individuals with one known parent but a missing other. For those cases, we assign a placeholder ID to the missing parent.

-
df_repaired <- checkParentIDs(df_got,addphantoms=TRUE,
-                              repair=TRUE,
-                              parentswithoutrow=FALSE,
-                              repairsex=FALSE
-                              ) %>% mutate(fam=1,
-                                           affected = case_when(ID %in% c(jon_id,dany_id) ~ 1,
-                                                               TRUE ~ 0)
-                              )
+
df_repaired <- checkParentIDs(df_got,addphantoms=TRUE,
+                              repair=TRUE,
+                              parentswithoutrow=FALSE,
+                              repairsex=FALSE
+                              ) %>% mutate(fam=1,
+                                           affected = case_when(ID %in% c(jon_id,dany_id, "365") ~ 1,
+                                                               TRUE ~ 0)
+                              )
## REPAIR IN EARLY ALPHA

This code creates new IDs for individuals with one known parent and a missing other. It checks if either momID or @@ -504,9 +536,9 @@

Plotting the Pedigree with incomplete parental information

Visualize the Pedigree

-
#fixParents(id=df_got$ID, dadid=df_got$dadID, momid=df_got$momID, sex=df_got$sex, missid = NA)
-
-plotPedigree(df_repaired,affected=df_repaired$affected,verbose=FALSE)
+
#fixParents(id=df_got$ID, dadid=df_got$dadID, momid=df_got$momID, sex=df_got$sex, missid = NA)
+
+plotPedigree(df_repaired,affected=df_repaired$affected,verbose=FALSE)

## Did not plot the following people: 85 88 125 142 228 229 258 259 274 275 305 336 357 381 388 405 409 418 420 424 428 451 487
## named list()
From 1d3a2dcb82981d508cf6907cb7bc9444d71aad79 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 19:35:40 -0400 Subject: [PATCH 07/11] styling --- R/checkParents.R | 135 ++++----- R/checkPedigree.R | 6 +- R/convertPedigree.R | 4 +- R/readGedcom.R | 96 +++---- R/readWikifamilytree.R | 27 +- data-raw/df_ASOIAF.R | 2 +- data-raw/df_potter.R | 328 +++++++++++----------- man/checkPedigreeNetwork.Rd | 6 +- man/readGedcom.Rd | 5 +- tests/testthat/test-calculateFamilySize.R | 9 +- tests/testthat/test-checkParents.R | 26 +- tests/testthat/test-convertPedigree.R | 22 +- tests/testthat/test-readPedigrees.R | 11 +- tests/testthat/test-tweakPedigree.R | 2 +- vignettes/ASOIAF.Rmd | 41 ++- vignettes/ASOIAF.html | 36 +-- 16 files changed, 384 insertions(+), 372 deletions(-) diff --git a/R/checkParents.R b/R/checkParents.R index b03a5926..eab8e0ca 100644 --- a/R/checkParents.R +++ b/R/checkParents.R @@ -23,8 +23,7 @@ checkParentIDs <- function(ped, verbose = FALSE, repair = FALSE, repairsex = repair, addphantoms = repair, - parentswithoutrow = repair - ) { + parentswithoutrow = repair) { # Standardize column names in the input dataframe ped_og <- ped ped <- standardizeColnames(ped) @@ -74,7 +73,7 @@ checkParentIDs <- function(ped, verbose = FALSE, repair = FALSE, cat("All parents are listed in the pedigree.\n") } } - validation_results$missing_parents <- validation_results$single_parents&length(rowless_parents) > 0 + validation_results$missing_parents <- validation_results$single_parents & length(rowless_parents) > 0 if (verbose) { cat("Step 2: Determining the if moms are the same sex and dads are same sex\n") @@ -146,11 +145,13 @@ checkParentIDs <- function(ped, verbose = FALSE, repair = FALSE, # Are any parents in both momID and dadID? momdad <- intersect(ped$dadID, ped$momID) - if (!is.na(momdad)&&length(momdad) > 0) { + if (!is.na(momdad) && length(momdad) > 0) { validation_results$parents_in_both <- momdad if (verbose) { - cat(paste("Some individuals appear in both momID and dadID roles.\n", - "These individuals are:\n")) + cat(paste( + "Some individuals appear in both momID and dadID roles.\n", + "These individuals are:\n" + )) print(momdad) } } @@ -162,12 +163,12 @@ checkParentIDs <- function(ped, verbose = FALSE, repair = FALSE, print(validation_results) } return(validation_results) - } else{ - if (verbose) { - cat("Validation Results:\n") - print(validation_results) - cat("Step 3: Attempting to repair missing parents...\n") - } + } else { + if (verbose) { + cat("Validation Results:\n") + print(validation_results) + cat("Step 3: Attempting to repair missing parents...\n") + } cat("REPAIR IN EARLY ALPHA\n") # Initialize a list to track changes made during repair @@ -177,32 +178,32 @@ checkParentIDs <- function(ped, verbose = FALSE, repair = FALSE, phantom_dads_added = c(), phantom_moms_added = c() ) - if(repairsex){ - # Fix sex of existing parents if wrong - mom_indices <- match(ped$momID, ped$ID) - dad_indices <- match(ped$dadID, ped$ID) + if (repairsex) { + # Fix sex of existing parents if wrong + mom_indices <- match(ped$momID, ped$ID) + dad_indices <- match(ped$dadID, ped$ID) - if (!is.na(validation_results$female_var)) { - corrected_moms <- ped$ID[mom_indices[!is.na(mom_indices)]] - ped$sex[mom_indices[!is.na(mom_indices)]] <- validation_results$female_var - changes$corrected_mom_sex <- corrected_moms - if (verbose && length(corrected_moms) > 0) { - cat("Corrected sex of moms for:", paste(corrected_moms, collapse = ", "), "\n") + if (!is.na(validation_results$female_var)) { + corrected_moms <- ped$ID[mom_indices[!is.na(mom_indices)]] + ped$sex[mom_indices[!is.na(mom_indices)]] <- validation_results$female_var + changes$corrected_mom_sex <- corrected_moms + if (verbose && length(corrected_moms) > 0) { + cat("Corrected sex of moms for:", paste(corrected_moms, collapse = ", "), "\n") + } } - } - if (!is.na(validation_results$male_var)) { - corrected_dads <- ped$ID[dad_indices[!is.na(dad_indices)]] - ped$sex[dad_indices[!is.na(dad_indices)]] <- validation_results$male_var - changes$corrected_dad_sex <- corrected_dads - if (verbose && length(corrected_dads) > 0) { - cat("Corrected sex of dads for:", paste(corrected_dads, collapse = ", "), "\n") + if (!is.na(validation_results$male_var)) { + corrected_dads <- ped$ID[dad_indices[!is.na(dad_indices)]] + ped$sex[dad_indices[!is.na(dad_indices)]] <- validation_results$male_var + changes$corrected_dad_sex <- corrected_dads + if (verbose && length(corrected_dads) > 0) { + cat("Corrected sex of dads for:", paste(corrected_dads, collapse = ", "), "\n") + } } } - } -} -if (addphantoms){ + } + if (addphantoms) { # Generate new IDs newIDbase <- if (is.numeric(ped$ID)) max(ped$ID, na.rm = TRUE) + 1 else paste0("phantom-", seq_len(nrow(ped))) new_entries <- data.frame() @@ -248,47 +249,47 @@ if (addphantoms){ if (verbose && length(changes$phantom_moms_added) > 0) { cat("Added phantom moms for:", paste(changes$phantom_moms_added, collapse = ", "), "\n") } -} + } # add phantom parents - if(parentswithoutrow){ - # Add parents who appear in momID or dadID but are missing from ID - listed_parents <- unique(c(ped$momID, ped$dadID)) - listed_parents <- listed_parents[!is.na(listed_parents)] - - existing_ids <- ped$ID - missing_parents <- setdiff(listed_parents, existing_ids) - - if (length(missing_parents) > 0) { - if (verbose) { - cat("Adding parents who were listed in momID/dadID but missing from ID:\n") - print(missing_parents) - } + if (parentswithoutrow) { + # Add parents who appear in momID or dadID but are missing from ID + listed_parents <- unique(c(ped$momID, ped$dadID)) + listed_parents <- listed_parents[!is.na(listed_parents)] + + existing_ids <- ped$ID + missing_parents <- setdiff(listed_parents, existing_ids) + + if (length(missing_parents) > 0) { + if (verbose) { + cat("Adding parents who were listed in momID/dadID but missing from ID:\n") + print(missing_parents) + } - for (pid in missing_parents) { - role <- unique( - c( - if (pid %in% ped$momID) "mom" else NULL, - if (pid %in% ped$dadID) "dad" else NULL + for (pid in missing_parents) { + role <- unique( + c( + if (pid %in% ped$momID) "mom" else NULL, + if (pid %in% ped$dadID) "dad" else NULL + ) ) - ) - inferred_sex <- if ("mom" %in% role) validation_results$female_var else validation_results$male_var - - new_row <- ped[1, ] - new_row$ID <- pid - new_row$dadID <- NA - new_row$momID <- NA - new_row$sex <- inferred_sex - new_entries <- rbind(new_entries, new_row) + inferred_sex <- if ("mom" %in% role) validation_results$female_var else validation_results$male_var + + new_row <- ped[1, ] + new_row$ID <- pid + new_row$dadID <- NA + new_row$momID <- NA + new_row$sex <- inferred_sex + new_entries <- rbind(new_entries, new_row) + } } - } - ped <- merge(ped, new_entries, all = TRUE) + ped <- merge(ped, new_entries, all = TRUE) } - if (verbose) { - cat("Changes Made:\n") - print(changes) - } - return(ped) + if (verbose) { + cat("Changes Made:\n") + print(changes) + } + return(ped) } #' Repair Parent IDs #' diff --git a/R/checkPedigree.R b/R/checkPedigree.R index 1ebe6afa..3c0af179 100644 --- a/R/checkPedigree.R +++ b/R/checkPedigree.R @@ -13,8 +13,10 @@ #' @return List containing detailed validation results. #' @examples #' \dontrun{ -#' results <- checkPedigreeNetwork(ped, personID = "ID", -#' momID = "momID", dadID = "dadID", verbose = TRUE) +#' results <- checkPedigreeNetwork(ped, +#' personID = "ID", +#' momID = "momID", dadID = "dadID", verbose = TRUE +#' ) #' } #' @export checkPedigreeNetwork <- function(ped, personID = "ID", momID = "momID", dadID = "dadID", verbose = FALSE) { diff --git a/R/convertPedigree.R b/R/convertPedigree.R index 257bc02f..a26f5f3b 100644 --- a/R/convertPedigree.R +++ b/R/convertPedigree.R @@ -212,7 +212,7 @@ ped2com <- function(ped, component, # isPar is the adjacency matrix. 'A' matrix from RAM if (component %in% c("common nuclear")) { Matrix::diag(isPar) <- 1 - if (sparse==FALSE) { + if (sparse == FALSE) { isPar <- as.matrix(isPar) } return(isPar) @@ -330,7 +330,7 @@ ped2com <- function(ped, component, # Assign 1 to all nonzero elements for mitochondrial component } - if (sparse==FALSE) { + if (sparse == FALSE) { r <- as.matrix(r) } if (flatten.diag) { # flattens diagonal if you don't want to deal with inbreeding diff --git a/R/readGedcom.R b/R/readGedcom.R index 1fd039ed..16251cef 100644 --- a/R/readGedcom.R +++ b/R/readGedcom.R @@ -9,6 +9,7 @@ #' @param combine_cols A logical value indicating whether to combine columns with duplicate values. #' @param verbose A logical value indicating whether to print messages. #' @param skinny A logical value indicating whether to return a skinny data frame. +#' @param ... Additional arguments to be passed to the function. #' @return A data frame containing information about individuals, with the following potential columns: #' - `id`: ID of the individual #' - `momID`: ID of the individual's mother @@ -51,7 +52,8 @@ readGedcom <- function(file_path, add_parents = TRUE, remove_empty_cols = TRUE, combine_cols = TRUE, - skinny = FALSE) { + skinny = FALSE, + ...) { # Checks if (!file.exists(file_path)) stop("File does not exist: ", file_path) if (verbose) { @@ -189,7 +191,7 @@ readGedcom <- function(file_path, # Attribute tags using process_tag() for (tag_field in list( - c("SEX", "sex"), + c("SEX", "sex"), # CAST caste # g7:CAST The name of an individual’s rank or status in society which is sometimes based on racial or religious differences, or differences in wealth, inherited rank, profession, or occupation. @@ -217,7 +219,7 @@ readGedcom <- function(file_path, # NMR number of marriages # g7:NMR The number of times this person has participated in a family as a spouse or parent. - c("NMR", "attribute_marriages"), + c("NMR", "attribute_marriages"), # OCCU occupation # g7:OCCU The type of work or profession of an individual. @@ -237,7 +239,7 @@ readGedcom <- function(file_path, # SSN social security number # g7:SSN A number assigned by the United States Social Security Administration, used for tax identification purposes. It is a type of IDNO. - c("SSN", "attribute_ssn"), + c("SSN", "attribute_ssn"), # TITL title # g7:INDI-TITL A formal designation used by an individual in connection with positions of royalty or other social status, such as Grand Duke. @@ -251,17 +253,19 @@ readGedcom <- function(file_path, # relationship data # g7:INDI-FAMC ## The family in which an individual appears as a child. It is also used with a g7:FAMC-STAT substructure to show individuals who are not children of the family. See FAMILY_RECORD for more details. -result <- process_tag("FAMC", "FAMC", num_rows, tmpv, vars, - extractor = function(x) stringr::str_extract(x, "(?<=@.)\\d*(?=@)"), - mode = "append") + result <- process_tag("FAMC", "FAMC", num_rows, tmpv, vars, + extractor = function(x) stringr::str_extract(x, "(?<=@.)\\d*(?=@)"), + mode = "append" + ) vars <- result$vars if (result$matched) next # FAMS (Family spouse) g7:FAMS # The family in which an individual appears as a partner. See FAMILY_RECORD for more details. result <- process_tag("FAMS", "FAMS", num_rows, tmpv, vars, - extractor = function(x) stringr::str_extract(x, "(?<=@.)\\d*(?=@)"), - mode = "append") + extractor = function(x) stringr::str_extract(x, "(?<=@.)\\d*(?=@)"), + mode = "append" + ) vars <- result$vars if (result$matched) next @@ -347,32 +351,32 @@ result <- process_tag("FAMC", "FAMC", num_rows, tmpv, vars, #' @return A list mapping family IDs to parent IDs. #' @keywords internal mapFAMS2parents <- function(df_temp) { - if (!all(c("FAMS", "sex") %in% colnames(df_temp))) { - warning("The data frame does not contain the necessary columns (FAMS, sex)") - return(NULL) - } - family_to_parents <- list() - for (i in 1:nrow(df_temp)) { - if (!is.na(df_temp$FAMS[i])) { - fams_ids <- unlist(strsplit(df_temp$FAMS[i], ", ")) - for (fams_id in fams_ids) { - if (!is.null(family_to_parents[[fams_id]])) { - if (df_temp$sex[i] == "M") { - family_to_parents[[fams_id]]$father <- df_temp$id[i] - } else if (df_temp$sex[i] == "F") { - family_to_parents[[fams_id]]$mother <- df_temp$id[i] - } - } else { - family_to_parents[[fams_id]] <- list() - if (df_temp$sex[i] == "M") { - family_to_parents[[fams_id]]$father <- df_temp$id[i] - } else if (df_temp$sex[i] == "F") { - family_to_parents[[fams_id]]$mother <- df_temp$id[i] - } + if (!all(c("FAMS", "sex") %in% colnames(df_temp))) { + warning("The data frame does not contain the necessary columns (FAMS, sex)") + return(NULL) + } + family_to_parents <- list() + for (i in 1:nrow(df_temp)) { + if (!is.na(df_temp$FAMS[i])) { + fams_ids <- unlist(strsplit(df_temp$FAMS[i], ", ")) + for (fams_id in fams_ids) { + if (!is.null(family_to_parents[[fams_id]])) { + if (df_temp$sex[i] == "M") { + family_to_parents[[fams_id]]$father <- df_temp$id[i] + } else if (df_temp$sex[i] == "F") { + family_to_parents[[fams_id]]$mother <- df_temp$id[i] + } + } else { + family_to_parents[[fams_id]] <- list() + if (df_temp$sex[i] == "M") { + family_to_parents[[fams_id]]$father <- df_temp$id[i] + } else if (df_temp$sex[i] == "F") { + family_to_parents[[fams_id]]$mother <- df_temp$id[i] } } } } + } return(family_to_parents) } @@ -388,22 +392,22 @@ mapFAMS2parents <- function(df_temp) { mapFAMC2parents <- function(df_temp, family_to_parents) { df_temp$momID <- NA_character_ df_temp$dadID <- NA_character_ - for (i in 1:nrow(df_temp)) { - if (!is.na(df_temp$FAMC[i])) { - famc_ids <- unlist(strsplit(df_temp$FAMC[i], ", ")) - for (famc_id in famc_ids) { - if (!is.null(family_to_parents[[famc_id]])) { - if (!is.null(family_to_parents[[famc_id]]$father)) { - df_temp$dadID[i] <- family_to_parents[[famc_id]]$father - } - if (!is.null(family_to_parents[[famc_id]]$mother)) { - df_temp$momID[i] <- family_to_parents[[famc_id]]$mother - } + for (i in 1:nrow(df_temp)) { + if (!is.na(df_temp$FAMC[i])) { + famc_ids <- unlist(strsplit(df_temp$FAMC[i], ", ")) + for (famc_id in famc_ids) { + if (!is.null(family_to_parents[[famc_id]])) { + if (!is.null(family_to_parents[[famc_id]]$father)) { + df_temp$dadID[i] <- family_to_parents[[famc_id]]$father + } + if (!is.null(family_to_parents[[famc_id]]$mother)) { + df_temp$momID[i] <- family_to_parents[[famc_id]]$mother } } } } - return(df_temp) + } + return(df_temp) } #' Process parents information @@ -545,9 +549,8 @@ process_tag <- function(tag, field_name, pattern_rows, line, vars, count_name <- paste0("num_", tolower(tag), "_rows") matched <- FALSE if (!is.null(pattern_rows[[count_name]]) && - pattern_rows[[count_name]] > 0 && - grepl(paste0(" ", tag), line)) { - + pattern_rows[[count_name]] > 0 && + grepl(paste0(" ", tag), line)) { value <- if (is.null(extractor)) extract_info(line, tag) else extractor(line) if (mode == "append" && !is.na(vars[[field_name]])) { @@ -560,4 +563,3 @@ process_tag <- function(tag, field_name, pattern_rows, line, vars, } return(list(vars = vars, matched = matched)) } - diff --git a/R/readWikifamilytree.R b/R/readWikifamilytree.R index 6d14c41a..fc0a7521 100644 --- a/R/readWikifamilytree.R +++ b/R/readWikifamilytree.R @@ -7,26 +7,25 @@ #' #' @return A list containing the summary, members, structure, and relationships of the family tree. #' @export -readWikifamilytree <- function(text=NULL, verbose = FALSE, file_path = NULL, ...) { +readWikifamilytree <- function(text = NULL, verbose = FALSE, file_path = NULL, ...) { # Checks if (is.null(text) && is.null(file_path)) { stop("Either 'text' or 'file_path' must be provided.") } - # read from file if provided -if (!is.null(file_path)){ + # read from file if provided + if (!is.null(file_path)) { + if (!file.exists(file_path)) stop("File does not exist: ", file_path) - if (!file.exists(file_path)) stop("File does not exist: ", file_path) - - if (verbose) { - print(paste("Reading file:", file_path)) - } - file <- data.frame(X1 = readLines(file_path)) - file_length <- nrow(file) - if (verbose) { - print(paste0("File is ", file_length, " lines long")) + if (verbose) { + print(paste("Reading file:", file_path)) + } + file <- data.frame(X1 = readLines(file_path)) + file_length <- nrow(file) + if (verbose) { + print(paste0("File is ", file_length, " lines long")) + } + text <- paste0(file$X1, collapse = "\n") } - text <- paste0(file$X1, collapse = "\n") -} # Extract summary text summary_text <- extractSummaryText(text) diff --git a/data-raw/df_ASOIAF.R b/data-raw/df_ASOIAF.R index e5cdc112..2874d8a8 100644 --- a/data-raw/df_ASOIAF.R +++ b/data-raw/df_ASOIAF.R @@ -10,7 +10,7 @@ library(BGmisc) ## Create dataframe ASOIAF <- readGedcom("data-raw/ASOIAF.ged") -#ASOIAF <- readGedcom("data-raw/ASOIAF_040725.ged") +# ASOIAF <- readGedcom("data-raw/ASOIAF_040725.ged") df <- ped2fam(ASOIAF, personID = "id") %>% select( diff --git a/data-raw/df_potter.R b/data-raw/df_potter.R index 77c147aa..153df4e4 100644 --- a/data-raw/df_potter.R +++ b/data-raw/df_potter.R @@ -156,18 +156,24 @@ df <- ped2fam(potter_big, personID = "id") %>% -birth_date, -FAMC, -FAMS - ) %>% rename(personID=id) %>% + ) %>% + rename(personID = id) %>% mutate( personID = as.numeric(personID), momID = as.numeric(momID), dadID = as.numeric(dadID), name = str_remove(name, "/") - ) %>% arrange(name) + ) %>% + arrange(name) -potter_clean <- potter %>% arrange(name) %>% mutate(personID = as.numeric(personID)*1000, - momID = as.numeric(momID)*1000, - dadID = as.numeric(dadID)*1000) +potter_clean <- potter %>% + arrange(name) %>% + mutate( + personID = as.numeric(personID) * 1000, + momID = as.numeric(momID) * 1000, + dadID = as.numeric(dadID) * 1000 + ) # merge so that I have all of A and all the Bs that match A @@ -177,166 +183,168 @@ df_clean <- df %>% potter_clean <- potter_clean %>% - mutate(personID = case_match( - personID, - 22000 ~ 15, - 9000 ~ 18, - 6000 ~ 10, - 18000 ~ 29, - 7000 ~ 1, - 17000 ~ 25, - 25000 ~ 27, - 13000 ~ 20, - 19000 ~ 34, - 23000 ~ 16, - 10000 ~ 17, - 14000 ~ 22, - 3000 ~ 8, - 24000 ~ 26, - 1000 ~ 9, - 26000 ~ 30, - 21000 ~ 14, - 11000 ~ 19, - 8000 ~ 13, - 5000 ~ 2, - 15000 ~ 23, - 16000 ~ 24, - 4000 ~ 3, - 2000 ~ -2000, - 101000 ~12, - 102000 ~ 11, - 20000 ~ -20000, - 27000 ~ -27000, - 106000 ~ 32, - 105000 ~ 33, - 29000 ~ 17, - 103000 ~ 7, - 104000 ~ 6, - 12000 ~ 21, - 30000 ~ -30000, - 28000 ~ -28000, - .default = personID - ), dadID = - case_match( - dadID, - 22000 ~ 15, - 9000 ~ 18, - 6000 ~ 10, - 18000 ~ 29, - 7000 ~ 1, - 17000 ~ 25, - 25000 ~ 27, - 13000 ~ 20, - 19000 ~ 34, - 23000 ~ 16, - 10000 ~ 17, - 14000 ~ 22, - 3000 ~ 8, - 24000 ~ 26, - 1000 ~ 9, - 26000 ~ 30, - 21000 ~ 14, - 11000 ~ 19, - 8000 ~ 13, - 5000 ~ 2, - 15000 ~ 23, - 16000 ~ 24, - 4000 ~ 3, - 2000 ~ 2000, - 101000 ~12, - 102000 ~ 11, - 20000 ~ 20000, - 27000 ~ 27000, - 106000 ~ 32, - 105000 ~ 33, - 29000 ~ 17, - 103000 ~ 7, - 104000 ~ 6, - 12000 ~ 21, - 30000 ~ 30000, - 28000 ~ 28000, - .default = dadID - ), momID = - case_match( - momID, - 22000 ~ 15, - 9000 ~ 18, - 6000 ~ 10, - 18000 ~ 29, - 7000 ~ 1, - 17000 ~ 25, - 25000 ~ 27, - 13000 ~ 20, - 19000 ~ 34, - 23000 ~ 16, - 10000 ~ 17, - 14000 ~ 22, - 3000 ~ 8, - 24000 ~ 26, - 1000 ~ 9, - 26000 ~ 30, - 21000 ~ 14, - 11000 ~ 19, - 8000 ~ 13, - 5000 ~ 2, - 15000 ~ 23, - 16000 ~ 24, - 4000 ~ 3, - 2000 ~ 2000, - 101000 ~12, - 102000 ~ 11, - 20000 ~ 20000, - 27000 ~ 27000, - 106000 ~ 32, - 105000 ~ 33, - 29000 ~ 17, - 103000 ~ 7, - 104000 ~ 6, - 12000 ~ 21, - 30000 ~ 30000, - 28000 ~ 28000, - .default = momID - ), spouseID = - case_match( - spouseID, - 22000 ~ 15, - 9000 ~ 18, - 6000 ~ 10, - 18000 ~ 29, - 7000 ~ 1, - 17000 ~ 25, - 25000 ~ 27, - 13000 ~ 20, - 19000 ~ 34, - 23000 ~ 16, - 10000 ~ 17, - 14000 ~ 22, - 3000 ~ 8, + mutate( + personID = case_match( + personID, + 22000 ~ 15, + 9000 ~ 18, + 6000 ~ 10, + 18000 ~ 29, + 7000 ~ 1, + 17000 ~ 25, + 25000 ~ 27, + 13000 ~ 20, + 19000 ~ 34, + 23000 ~ 16, + 10000 ~ 17, + 14000 ~ 22, + 3000 ~ 8, 24000 ~ 26, - 1000 ~ 9, - 26000 ~ 30, + 1000 ~ 9, + 26000 ~ 30, 21000 ~ 14, - 11000 ~ 19, - 8000 ~ 13, + 11000 ~ 19, + 8000 ~ 13, 5000 ~ 2, - 15000 ~ 23, - 16000 ~ 24, + 15000 ~ 23, + 16000 ~ 24, 4000 ~ 3, - 2000 ~ 2000, - 101000 ~12, - 102000 ~ 11, - 20000 ~ 20000, - 27000 ~ 27000, + 2000 ~ -2000, + 101000 ~ 12, + 102000 ~ 11, + 20000 ~ -20000, + 27000 ~ -27000, 106000 ~ 32, - 105000 ~ 33, - 29000 ~ 17, - 103000 ~ 7, - 104000 ~ 6, - 12000 ~ 21, - 30000 ~ 30000, - 28000 ~ 28000, - .default = spouseID - )) + 105000 ~ 33, + 29000 ~ 17, + 103000 ~ 7, + 104000 ~ 6, + 12000 ~ 21, + 30000 ~ -30000, + 28000 ~ -28000, + .default = personID + ), dadID = + case_match( + dadID, + 22000 ~ 15, + 9000 ~ 18, + 6000 ~ 10, + 18000 ~ 29, + 7000 ~ 1, + 17000 ~ 25, + 25000 ~ 27, + 13000 ~ 20, + 19000 ~ 34, + 23000 ~ 16, + 10000 ~ 17, + 14000 ~ 22, + 3000 ~ 8, + 24000 ~ 26, + 1000 ~ 9, + 26000 ~ 30, + 21000 ~ 14, + 11000 ~ 19, + 8000 ~ 13, + 5000 ~ 2, + 15000 ~ 23, + 16000 ~ 24, + 4000 ~ 3, + 2000 ~ 2000, + 101000 ~ 12, + 102000 ~ 11, + 20000 ~ 20000, + 27000 ~ 27000, + 106000 ~ 32, + 105000 ~ 33, + 29000 ~ 17, + 103000 ~ 7, + 104000 ~ 6, + 12000 ~ 21, + 30000 ~ 30000, + 28000 ~ 28000, + .default = dadID + ), momID = + case_match( + momID, + 22000 ~ 15, + 9000 ~ 18, + 6000 ~ 10, + 18000 ~ 29, + 7000 ~ 1, + 17000 ~ 25, + 25000 ~ 27, + 13000 ~ 20, + 19000 ~ 34, + 23000 ~ 16, + 10000 ~ 17, + 14000 ~ 22, + 3000 ~ 8, + 24000 ~ 26, + 1000 ~ 9, + 26000 ~ 30, + 21000 ~ 14, + 11000 ~ 19, + 8000 ~ 13, + 5000 ~ 2, + 15000 ~ 23, + 16000 ~ 24, + 4000 ~ 3, + 2000 ~ 2000, + 101000 ~ 12, + 102000 ~ 11, + 20000 ~ 20000, + 27000 ~ 27000, + 106000 ~ 32, + 105000 ~ 33, + 29000 ~ 17, + 103000 ~ 7, + 104000 ~ 6, + 12000 ~ 21, + 30000 ~ 30000, + 28000 ~ 28000, + .default = momID + ), spouseID = + case_match( + spouseID, + 22000 ~ 15, + 9000 ~ 18, + 6000 ~ 10, + 18000 ~ 29, + 7000 ~ 1, + 17000 ~ 25, + 25000 ~ 27, + 13000 ~ 20, + 19000 ~ 34, + 23000 ~ 16, + 10000 ~ 17, + 14000 ~ 22, + 3000 ~ 8, + 24000 ~ 26, + 1000 ~ 9, + 26000 ~ 30, + 21000 ~ 14, + 11000 ~ 19, + 8000 ~ 13, + 5000 ~ 2, + 15000 ~ 23, + 16000 ~ 24, + 4000 ~ 3, + 2000 ~ 2000, + 101000 ~ 12, + 102000 ~ 11, + 20000 ~ 20000, + 27000 ~ 27000, + 106000 ~ 32, + 105000 ~ 33, + 29000 ~ 17, + 103000 ~ 7, + 104000 ~ 6, + 12000 ~ 21, + 30000 ~ 30000, + 28000 ~ 28000, + .default = spouseID + ) + ) # Left join by name diff --git a/man/checkPedigreeNetwork.Rd b/man/checkPedigreeNetwork.Rd index d6e92749..9b8c8d24 100644 --- a/man/checkPedigreeNetwork.Rd +++ b/man/checkPedigreeNetwork.Rd @@ -33,7 +33,9 @@ Checks for structural issues in pedigree networks, including: } \examples{ \dontrun{ -results <- checkPedigreeNetwork(ped, personID = "ID", -momID = "momID", dadID = "dadID", verbose = TRUE) +results <- checkPedigreeNetwork(ped, + personID = "ID", + momID = "momID", dadID = "dadID", verbose = TRUE +) } } diff --git a/man/readGedcom.Rd b/man/readGedcom.Rd index 09d19040..78d45773 100644 --- a/man/readGedcom.Rd +++ b/man/readGedcom.Rd @@ -10,7 +10,8 @@ readGedcom( add_parents = TRUE, remove_empty_cols = TRUE, combine_cols = TRUE, - skinny = FALSE + skinny = FALSE, + ... ) } \arguments{ @@ -25,6 +26,8 @@ readGedcom( \item{combine_cols}{A logical value indicating whether to combine columns with duplicate values.} \item{skinny}{A logical value indicating whether to return a skinny data frame.} + +\item{...}{Additional arguments to be passed to the function.} } \value{ A data frame containing information about individuals, with the following potential columns: diff --git a/tests/testthat/test-calculateFamilySize.R b/tests/testthat/test-calculateFamilySize.R index b6e32c7d..f648c8fd 100644 --- a/tests/testthat/test-calculateFamilySize.R +++ b/tests/testthat/test-calculateFamilySize.R @@ -65,13 +65,12 @@ test_that("allGens behaves with Ngens", { result <- allGens(kpc = kpc, Ngen = Ngen, marR = marR) - expect_equal(result, 2) + expect_equal(result, 2) Ngen <- 0 - expect_error( allGens(kpc = kpc, Ngen = Ngen, marR = marR)) + expect_error(allGens(kpc = kpc, Ngen = Ngen, marR = marR)) - expect_error(allGens(kpc = kpc, Ngen= NULL, marR = marR)) - expect_error(allGens(kpc = kpc, Ngen= -1, marR = marR)) + expect_error(allGens(kpc = kpc, Ngen = NULL, marR = marR)) + expect_error(allGens(kpc = kpc, Ngen = -1, marR = marR)) }) - diff --git a/tests/testthat/test-checkParents.R b/tests/testthat/test-checkParents.R index 86776577..21660c4b 100644 --- a/tests/testthat/test-checkParents.R +++ b/tests/testthat/test-checkParents.R @@ -1,29 +1,27 @@ # Test Case 1: Validate sex coding without repair test_that("checkParentIDs identifies parent coding correctly in potter dataset", { - results <- checkParentIDs(potter,verbose = TRUE, repair = FALSE) + results <- checkParentIDs(potter, verbose = TRUE, repair = FALSE) expect_false("parents_in_both" %in% names(results)) expect_false(results$single_parents) expect_false(results$missing_parents) expect_true(results$female_moms) expect_true(results$male_dads) - expect_equal(results$female_var,0) - expect_equal(results$male_var,1) - expect_equal(results$mom_sex,0) - expect_equal(results$dad_sex,1) + expect_equal(results$female_var, 0) + expect_equal(results$male_var, 1) + expect_equal(results$mom_sex, 0) + expect_equal(results$dad_sex, 1) }) # Test Case 2: Validate sex coding without repair test_that("checksif single parents found correctly in ASOIAF dataset", { data(ASOIAF) df_asoiaf <- ASOIAF - results <- checkParentIDs(df_asoiaf,verbose = FALSE, repair = FALSE) + results <- checkParentIDs(df_asoiaf, verbose = FALSE, repair = FALSE) expect_true(results$single_parents) - single_dads <- length(df_asoiaf$id[!is.na(df_asoiaf$dadID)&is.na(df_asoiaf$momID)]) - single_moms <- length(df_asoiaf$id[is.na(df_asoiaf$dadID)&!is.na(df_asoiaf$momID)]) - expect_equal(single_moms, length(results$missing_fathers)) - expect_equal(single_dads, length(results$missing_mothers)) - repaired_df <- checkParentIDs(df_asoiaf,verbose = FALSE, repair = TRUE) - expect_equal(nrow(repaired_df), nrow(df_asoiaf)+single_moms+single_dads) - + single_dads <- length(df_asoiaf$id[!is.na(df_asoiaf$dadID) & is.na(df_asoiaf$momID)]) + single_moms <- length(df_asoiaf$id[is.na(df_asoiaf$dadID) & !is.na(df_asoiaf$momID)]) + expect_equal(single_moms, length(results$missing_fathers)) + expect_equal(single_dads, length(results$missing_mothers)) + repaired_df <- checkParentIDs(df_asoiaf, verbose = FALSE, repair = TRUE) + expect_equal(nrow(repaired_df), nrow(df_asoiaf) + single_moms + single_dads) }) - diff --git a/tests/testthat/test-convertPedigree.R b/tests/testthat/test-convertPedigree.R index fb6e06fe..11dfbeca 100644 --- a/tests/testthat/test-convertPedigree.R +++ b/tests/testthat/test-convertPedigree.R @@ -1,7 +1,7 @@ test_that("ped2add produces correct matrix dims, values, and dimnames for hazard", { tolerance <- 1e-10 data(hazard) - add <- ped2add(hazard,sparse = FALSE) + add <- ped2add(hazard, sparse = FALSE) # Check dimension expect_equal(dim(add), c(nrow(hazard), nrow(hazard))) # Check several values @@ -21,7 +21,7 @@ test_that("ped2add produces correct matrix dims, values, and dimnames for hazard test_that("ped2add produces correct matrix dims, values, and dimnames for alternative transpose", { tolerance <- 1e-10 data(hazard) - add <- ped2add(hazard, tcross.alt.crossprod = TRUE,sparse = FALSE) + add <- ped2add(hazard, tcross.alt.crossprod = TRUE, sparse = FALSE) # Check dimension expect_equal(dim(add), c(nrow(hazard), nrow(hazard)), tolerance = tolerance) # Check several values @@ -42,7 +42,7 @@ test_that("ped2add produces correct matrix dims, values, and dimnames for altern test_that("ped2add produces correct matrix dims, values, and dimnames for inbreeding data", { tolerance <- 1e-10 data(inbreeding) - add <- ped2add(inbreeding,sparse = FALSE) + add <- ped2add(inbreeding, sparse = FALSE) # Check dimension expect_equal(dim(add), c(nrow(inbreeding), nrow(inbreeding)), tolerance = tolerance) # Check several values @@ -62,7 +62,7 @@ test_that("ped2add produces correct matrix dims, values, and dimnames for inbree test_that("ped2add produces correct matrix dims, values, and dimnames for inbreeding data with alternative transpose", { tolerance <- 1e-10 data(inbreeding) - add <- ped2add(inbreeding, transpose_method = "star",sparse = FALSE) + add <- ped2add(inbreeding, transpose_method = "star", sparse = FALSE) # Check dimension expect_equal(dim(add), c(nrow(inbreeding), nrow(inbreeding))) # Check several values @@ -81,7 +81,7 @@ test_that("ped2add produces correct matrix dims, values, and dimnames for inbree test_that("ped2add produces correct matrix dims, values, and dimnames for inbreeding data with 2nd alternative transpose", { tolerance <- 1e-10 data(inbreeding) - add <- ped2add(inbreeding, transpose_method = "crossprod",sparse = FALSE) + add <- ped2add(inbreeding, transpose_method = "crossprod", sparse = FALSE) # Check dimension expect_equal(dim(add), c(nrow(inbreeding), nrow(inbreeding))) # Check several values @@ -101,7 +101,7 @@ test_that("ped2add produces correct matrix dims, values, and dimnames for inbree test_that("ped2add flattens diagonal for inbreeding data", { tolerance <- 1e-10 data(inbreeding) - add <- ped2add(inbreeding, flatten.diag = TRUE,sparse = FALSE) + add <- ped2add(inbreeding, flatten.diag = TRUE, sparse = FALSE) # Check dimension expect_equal(dim(add), c(nrow(inbreeding), nrow(inbreeding)), tolerance = tolerance) # Check several values @@ -121,7 +121,7 @@ test_that("ped2mit produces correct matrix dims, values, and dimnames for inbree tolerance <- 1e-10 # Check dimension data(inbreeding) - mit <- ped2mit(inbreeding,sparse = FALSE) + mit <- ped2mit(inbreeding, sparse = FALSE) # Check dimension expect_equal(dim(mit), c(nrow(inbreeding), nrow(inbreeding))) # Check several values @@ -142,7 +142,7 @@ test_that("ped2mit produces correct matrix dims, values, and dimnames for inbree tolerance <- 1e-10 # Check dimension data(inbreeding) - mit <- ped2mit(inbreeding,sparse = FALSE) + mit <- ped2mit(inbreeding, sparse = FALSE) # Check dimension expect_equal(dim(mit), c(nrow(inbreeding), nrow(inbreeding)), tolerance = tolerance) # Check several values @@ -164,7 +164,7 @@ test_that("ped2cn produces correct matrix dims, values, and dimnames", { # Check dimension data(inbreeding) - cn <- ped2cn(inbreeding,sparse = FALSE) + cn <- ped2cn(inbreeding, sparse = FALSE) expect_equal(dim(cn), c( nrow(inbreeding), nrow(inbreeding) @@ -189,7 +189,7 @@ test_that("ped2cn produces correct matrix dims, values, and dimnames", { test_that("ped2ce produces correct matrix dims, values, and dimnames", { tolerance <- 1e-10 data(inbreeding) - ce <- ped2ce(inbreeding,sparse = FALSE) + ce <- ped2ce(inbreeding, sparse = FALSE) expect_equal(dim(ce), c(nrow(inbreeding), nrow(inbreeding)), tolerance = tolerance) # Check several values # expect_true(all(diag(ce) == 1)) @@ -207,7 +207,7 @@ test_that("ped2ce produces correct matrix dims, values, and dimnames", { test_that("ped2add verbose prints updates", { data(hazard) - expect_output(ped2add(hazard, verbose = TRUE,sparse = FALSE), regexp = "Family Size =") + expect_output(ped2add(hazard, verbose = TRUE, sparse = FALSE), regexp = "Family Size =") }) diff --git a/tests/testthat/test-readPedigrees.R b/tests/testthat/test-readPedigrees.R index da87fc25..349b4f30 100644 --- a/tests/testthat/test-readPedigrees.R +++ b/tests/testthat/test-readPedigrees.R @@ -193,12 +193,12 @@ test_that("readWikifamilytree reads a string correctly", { {{familytree | JOE | | ME | | SIS | | | JOE=My brother Joe|ME='''Me!'''|SIS=My little sister}} {{familytree/end}}" - temp_file <- tempfile(fileext = ".txt") + temp_file <- tempfile(fileext = ".txt") writeLines(family_tree_text, temp_file) - result <- readWikifamilytree(text=family_tree_text) - result2 <- readWikifamilytree(file_path=temp_file) + result <- readWikifamilytree(text = family_tree_text) + result2 <- readWikifamilytree(file_path = temp_file) expect_equal( result$summary, @@ -217,8 +217,7 @@ test_that("readWikifamilytree reads a string correctly", { test_that("readWikifamilytree reads a file correctly", { # Create a temporary WikiFamilyTree file for testing # Example usage - family_tree_file_path <- "data-raw/Targaryen tree Dance.txt" #system.file("extdata", "Targaryen tree Dance.txt", package = "BGmisc") - -# result <- readWikifamilytree(file_path=family_tree_file_path) + family_tree_file_path <- "data-raw/Targaryen tree Dance.txt" # system.file("extdata", "Targaryen tree Dance.txt", package = "BGmisc") + # result <- readWikifamilytree(file_path=family_tree_file_path) }) diff --git a/tests/testthat/test-tweakPedigree.R b/tests/testthat/test-tweakPedigree.R index 24f81ff9..0b542e2e 100644 --- a/tests/testthat/test-tweakPedigree.R +++ b/tests/testthat/test-tweakPedigree.R @@ -115,7 +115,7 @@ test_that("makeInbreeding - Inbred mates specified by generation and cousin", { expect_error(makeInbreeding(ped, gen_inbred = gen_inbred, - type_inbred = type_inbred,verbose = TRUE + type_inbred = type_inbred, verbose = TRUE ), regexp = "Cousin inbreedin") }) diff --git a/vignettes/ASOIAF.Rmd b/vignettes/ASOIAF.Rmd index 69d72a76..f9dceaa0 100644 --- a/vignettes/ASOIAF.Rmd +++ b/vignettes/ASOIAF.Rmd @@ -42,7 +42,6 @@ df_got <- checkSex(ASOIAF, code_female = 0, verbose = FALSE, repair = TRUE ) - ``` @@ -94,9 +93,9 @@ For interpretability, we convert these square matrices into long-format tables u ```{r} df_links <- com2links( writetodisk = FALSE, - ad_ped_matrix = add, cn_ped_matrix = cn, mit_ped_matrix= mt, + ad_ped_matrix = add, cn_ped_matrix = cn, mit_ped_matrix = mt, drop_upper_triangular = TRUE -)# %>% +) # %>% # filter(ID1 != ID2) ``` @@ -108,7 +107,6 @@ We next identify the rows in the pairwise relatedness table that correspond to J ```{r} - # Find the IDs of Jon Snow and Daenerys Targaryen jon_id <- df_got %>% @@ -123,12 +121,11 @@ dany_id <- df_got %>% Then we isolate their dyad: ```{r} - jon_dany_row <- df_links %>% filter(ID1 == jon_id | ID2 == jon_id) %>% - filter(ID1 %in% dany_id| ID2 %in% dany_id) + filter(ID1 %in% dany_id | ID2 %in% dany_id) -jon_dany_row +jon_dany_row ``` This table contains the additive and nuclear relatedness estimates for Jon and Daenerys. If the pedigree reflects their canonical aunt-nephew relationship and is free from inbreeding, we’d expect to see an additive coefficient close to 0.25. However, the value is `r jon_dany_row$addRel[1]`, indicating a more complex relationship. @@ -146,18 +143,18 @@ Many real-world and fictional pedigrees contain individuals with unknown or part To facilitate plotting, we check for individuals with one known parent but a missing other. For those cases, we assign a placeholder ID to the missing parent. ```{r} - -df_repaired <- checkParentIDs(df_got,addphantoms=TRUE, - repair=TRUE, - parentswithoutrow=FALSE, - repairsex=FALSE - ) %>% mutate(fam=1, - affected = case_when(ID %in% c(jon_id,dany_id, "365") ~ 1, - TRUE ~ 0) - ) - - - +df_repaired <- checkParentIDs(df_got, + addphantoms = TRUE, + repair = TRUE, + parentswithoutrow = FALSE, + repairsex = FALSE +) %>% mutate( + fam = 1, + affected = case_when( + ID %in% c(jon_id, dany_id, "365") ~ 1, + TRUE ~ 0 + ) +) ``` This code creates new IDs for individuals with one known parent and a missing other. It checks if either `momID` or `dadID` is missing, and if so, it assigns a new ID based on the row number. This allows us to visualize the pedigree even when some parental information is incomplete. @@ -167,9 +164,7 @@ This code creates new IDs for individuals with one known parent and a missing ot ## Visualize the Pedigree ```{r} +# fixParents(id=df_got$ID, dadid=df_got$dadID, momid=df_got$momID, sex=df_got$sex, missid = NA) -#fixParents(id=df_got$ID, dadid=df_got$dadID, momid=df_got$momID, sex=df_got$sex, missid = NA) - -plotPedigree(df_repaired,affected=df_repaired$affected,verbose=FALSE) - +plotPedigree(df_repaired, affected = df_repaired$affected, verbose = FALSE) ``` diff --git a/vignettes/ASOIAF.html b/vignettes/ASOIAF.html index 7e1f9f05..6762cd1f 100644 --- a/vignettes/ASOIAF.html +++ b/vignettes/ASOIAF.html @@ -360,13 +360,13 @@

Load Packages and Data

structure of the built-in ASOIAF pedigree.

library(BGmisc)
 library(tidyverse)
-
## ── Attaching core tidyverse packages ─────────────────
+
## ── Attaching core tidyverse packages ────── tidyverse 2.0.0 ──
 ## ✔ dplyr     1.1.4     ✔ readr     2.1.5
 ## ✔ forcats   1.0.0     ✔ stringr   1.5.1
 ## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
 ## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
 ## ✔ purrr     1.0.4     
-## ── Conflicts ──────────────── tidyverse_conflicts() ──
+## ── Conflicts ──────────────────────── tidyverse_conflicts() ──
 ## ✖ dplyr::between()     masks BGmisc::between()
 ## ✖ dplyr::filter()      masks stats::filter()
 ## ✖ dplyr::first()       masks BGmisc::first()
@@ -462,9 +462,9 @@ 

Convert to Pairwise Format

including their additive and common nuclear coefficients.

df_links <- com2links(
   writetodisk = FALSE,
-  ad_ped_matrix = add, cn_ped_matrix = cn, mit_ped_matrix= mt,
+  ad_ped_matrix = add, cn_ped_matrix = cn, mit_ped_matrix = mt,
   drop_upper_triangular = TRUE
-)# %>%
+) # %>%
## 'as(<dsCMatrix>, "dgCMatrix")' is deprecated.
 ## Use 'as(., "generalMatrix")' instead.
 ## See help("Deprecated") and help("Matrix-deprecated").
@@ -492,9 +492,9 @@

Locate Jon and Daenerys

Then we isolate their dyad:

jon_dany_row <- df_links %>%
   filter(ID1 == jon_id | ID2 == jon_id) %>%
-  filter(ID1 %in% dany_id| ID2 %in% dany_id)
+  filter(ID1 %in% dany_id | ID2 %in% dany_id)
 
-jon_dany_row 
+jon_dany_row
##   ID1 ID2     addRel mitRel cnuRel
 ## 1 206 211 0.31274414      0      0
 ## 2 211 304 0.01953125      0      0
@@ -519,14 +519,18 @@

Plotting the Pedigree with Incomplete Parental Information

To facilitate plotting, we check for individuals with one known parent but a missing other. For those cases, we assign a placeholder ID to the missing parent.

-
df_repaired <- checkParentIDs(df_got,addphantoms=TRUE,
-                              repair=TRUE,
-                              parentswithoutrow=FALSE,
-                              repairsex=FALSE
-                              ) %>% mutate(fam=1,
-                                           affected = case_when(ID %in% c(jon_id,dany_id, "365") ~ 1,
-                                                               TRUE ~ 0)
-                              )
+
df_repaired <- checkParentIDs(df_got,
+  addphantoms = TRUE,
+  repair = TRUE,
+  parentswithoutrow = FALSE,
+  repairsex = FALSE
+) %>% mutate(
+  fam = 1,
+  affected = case_when(
+    ID %in% c(jon_id, dany_id, "365") ~ 1,
+    TRUE ~ 0
+  )
+)
## REPAIR IN EARLY ALPHA

This code creates new IDs for individuals with one known parent and a missing other. It checks if either momID or @@ -536,9 +540,9 @@

Plotting the Pedigree with Incomplete Parental Information

Visualize the Pedigree

-
#fixParents(id=df_got$ID, dadid=df_got$dadID, momid=df_got$momID, sex=df_got$sex, missid = NA)
+
# fixParents(id=df_got$ID, dadid=df_got$dadID, momid=df_got$momID, sex=df_got$sex, missid = NA)
 
-plotPedigree(df_repaired,affected=df_repaired$affected,verbose=FALSE)
+plotPedigree(df_repaired, affected = df_repaired$affected, verbose = FALSE)

## Did not plot the following people: 85 88 125 142 228 229 258 259 274 275 305 336 357 381 388 405 409 418 420 424 428 451 487
## named list()
From c3c9c3ba67982528f255d81b92baf3fea4050b20 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 20:50:35 -0400 Subject: [PATCH 08/11] refactor summary subfunctions --- R/buildPedigree.R | 17 +++-- R/computeRelatedness.R | 3 - R/convertPedigree.R | 32 ++++++--- R/makeLinks.R | 63 +++++++++++++---- R/readGedcom.R | 2 + R/summarizePedigree.R | 86 +++++++++++++++++------- data-raw/benchmark.R | 3 +- data-raw/df_ASOIAF.R | 3 +- data-raw/df_inbreeding.R | 2 +- data-raw/df_potter.R | 7 +- man/findBiggest.Rd | 18 +++++ man/findOldest.Rd | 20 ++++++ man/isChild.Rd | 19 ++++++ man/ped2fam.Rd | 3 +- man/ped2maternal.Rd | 2 +- man/summarizeFamilies.Rd | 2 +- man/summarizeMatrilines.Rd | 2 +- man/summarizePatrilines.Rd | 2 +- man/summarizePedigrees.Rd | 2 +- tests/testthat/test-computeRelatedness.R | 10 +++ tests/testthat/test-summarizePedigrees.R | 31 ++++++--- 21 files changed, 250 insertions(+), 79 deletions(-) create mode 100644 man/findBiggest.Rd create mode 100644 man/findOldest.Rd create mode 100644 man/isChild.Rd diff --git a/R/buildPedigree.R b/R/buildPedigree.R index 6a89f94e..3d94ecad 100644 --- a/R/buildPedigree.R +++ b/R/buildPedigree.R @@ -1,6 +1,7 @@ #' Segment Pedigree into Extended Families #' -#' This function adds an extended family ID variable to a pedigree by segmenting that dataset into independent extended families +#' This function adds an extended family ID variable to a pedigree by segmenting +#' that dataset into independent extended families #' using the weakly connected components algorithm. #' #' @@ -86,7 +87,8 @@ ped2graph <- function(ped, adjacent = c("parents", "mothers", "fathers"), ...) { # Check ped/data.fram - if (!inherits(ped, "data.frame")) stop("ped should be a data.frame or inherit to a data.frame") + if (!inherits(ped, "data.frame")) { + stop("ped should be a data.frame or inherit to a data.frame")} # Handle adjacent argument adjacent <- match.arg(tolower(adjacent)[1], choices = c( @@ -165,7 +167,7 @@ ped2graph <- function(ped, #' Add a maternal line ID variable to a pedigree #' @inheritParams ped2fam -#' @param matID Character. Maternal line ID variable to be created and added to the pedigree +#' @param matID Character. Maternal line ID variable to be created and added to the pedigree #' @details #' Under various scenarios it is useful to know which people in a pedigree #' belong to the same maternal lines. This function first turns a pedigree @@ -177,9 +179,11 @@ ped2graph <- function(ped, #' @export #' ped2maternal <- function(ped, personID = "ID", - momID = "momID", dadID = "dadID", matID = "matID", ...) { + momID = "momID", dadID = "dadID", + matID = "matID", ...) { # Call to wrapper function - .ped2id(ped = ped, personID = personID, momID = momID, dadID = dadID, famID = matID, type = "mothers") + .ped2id(ped = ped, personID = personID, momID = momID, + dadID = dadID, famID = matID, type = "mothers") } #' Add a paternal line ID variable to a pedigree @@ -199,5 +203,6 @@ ped2paternal <- function(ped, personID = "ID", momID = "momID", dadID = "dadID", patID = "patID", ...) { # Call to wrapper function - .ped2id(ped = ped, personID = personID, momID = momID, dadID = dadID, famID = patID, type = "fathers") + .ped2id(ped = ped, personID = personID, momID = momID, + dadID = dadID, famID = patID, type = "fathers") } diff --git a/R/computeRelatedness.R b/R/computeRelatedness.R index 2533c16f..e3f53b8b 100644 --- a/R/computeRelatedness.R +++ b/R/computeRelatedness.R @@ -133,7 +133,6 @@ calculateH <- function(r1, r2, obsR1, obsR2) { message("Your scale might be reverse coded because you have negative correlations. Please check your data. ") } - # Calculate heritability estimates (H^2) for all pairs heritability_estimates <- (obsR1 - obsR2) / (r1 - r2) @@ -145,7 +144,5 @@ calculateH <- function(r1, r2, obsR1, obsR2) { if (any(heritability_estimates > 1)) { warning("Some calculated heritability values are greater than 1, which may suggest overestimation or errors in the observed correlations or relatedness coefficients.") } - - return(heritability_estimates) } diff --git a/R/convertPedigree.R b/R/convertPedigree.R index a26f5f3b..0f454acc 100644 --- a/R/convertPedigree.R +++ b/R/convertPedigree.R @@ -95,7 +95,7 @@ ped2com <- function(ped, component, # standardize colnames if (standardize.colnames) { - ped <- standardizeColnames(ped) + ped <- standardizeColnames(ped, verbose = verbose) } # Load final result if computation was completed @@ -224,15 +224,8 @@ ped2com <- function(ped, component, } else { # isChild is the 'S' matrix from RAM - if (isChild_method == "partialparent") { - isChild <- apply(ped[, c("momID", "dadID")], 1, function(x) { - .5 + .25 * sum(is.na(x)) # 2 parents -> .5, 1 parent -> .75, 0 parents -> 1 - }) - } else { - isChild <- apply(ped[, c("momID", "dadID")], 1, function(x) { - 2^(-!all(is.na(x))) - }) - } + isChild <- isChild(isChild_method=isChild_method, ped=ped) + if (saveable) { saveRDS(isChild, file = checkpoint_files$isChild) } @@ -723,3 +716,22 @@ compute_parent_adjacency <- function(ped, component, } return(list_of_adjacency) } + + +#' Determine isChild Status, isChild is the 'S' matrix from RAM +#' @param isChild_method method to determine isChild status +#' @param ped pedigree data frame +#' @return isChild 'S' matrix +#' + +isChild <- function(isChild_method, ped) { + if (isChild_method == "partialparent") { + isChild <- apply(ped[, c("momID", "dadID")], 1, function(x) { + .5 + .25 * sum(is.na(x)) # 2 parents -> .5, 1 parent -> .75, 0 parents -> 1 + }) + } else { + isChild <- apply(ped[, c("momID", "dadID")], 1, function(x) { + 2^(-!all(is.na(x))) + }) + } +} diff --git a/R/makeLinks.R b/R/makeLinks.R index 9517b533..fe40bc59 100644 --- a/R/makeLinks.R +++ b/R/makeLinks.R @@ -508,6 +508,44 @@ com2links <- function( # return(NULL) } } else if (legacy) { + # --- Legacy Mode --- + # In legacy mode, convert matrices to the expected symmetric formats. + + com2links.legacy( + rel_pairs_file = rel_pairs_file, + ad_ped_matrix = ad_ped_matrix, + mit_ped_matrix = mit_ped_matrix, + cn_ped_matrix = cn_ped_matrix, + update_rate = update_rate, + verbose = verbose, + outcome_name = outcome_name + ) + return(NULL) + } + + # --- End of Legacy Mode --- + + # Merge and write the parentage matrices + # df <- full_join(mat_ped_matrix %>% arrange(ID), pat_ped_matrix %>% arrange(ID)) + + # write.table(df, file = mapa_id_file, sep = ",", append = FALSE, row.names = FALSE) +} + +#' @title com2links +#' @description +#' This legacy function converts pedigree matrices into a related pairs file. + + +com2links.legacy <- function( + rel_pairs_file = "dataRelatedPairs.csv", + ad_ped_matrix = NULL, + mit_ped_matrix = mt_ped_matrix, + mt_ped_matrix = NULL, + cn_ped_matrix = NULL, + update_rate = 500, + verbose = FALSE, + outcome_name = "data", + ...) { # --- Legacy Mode --- if (verbose) { message("Using legacy mode") @@ -536,8 +574,12 @@ com2links <- function( fname <- paste0(outcome_name, "_dataBiggestRelatedPairsTake2.csv") } # Initialize the output file with headers. - ds <- data.frame(ID1 = numeric(0), ID2 = numeric(0), addRel = numeric(0), mitRel = numeric(0), cnuRel = numeric(0)) - utils::write.table(ds, file = fname, sep = ",", append = FALSE, row.names = FALSE) + ds <- data.frame(ID1 = numeric(0), ID2 = numeric(0), + addRel = numeric(0), + mitRel = numeric(0), cnuRel = numeric(0)) + + utils::write.table(ds, file = fname, sep = ",", + append = FALSE, row.names = FALSE) # Extract IDs from the common nuclear matrix. ids <- as.numeric(dimnames(biggestCnPed)[[1]]) @@ -587,7 +629,8 @@ com2links <- function( # browser() if (cond1 || cond2 || cond3) { ID1 <- ids[u] - tds <- data.frame(ID1 = ID1, ID2 = ID2, addRel = 0, mitRel = 0, cnuRel = 0) + tds <- data.frame(ID1 = ID1, ID2 = ID2, + addRel = 0, mitRel = 0, cnuRel = 0) if (cond1) { tds$addRel[u %in% iss1vv] <- biggestPed@x[vv1] } @@ -597,18 +640,12 @@ com2links <- function( if (cond3) { tds$cnuRel[u %in% iss3vv] <- biggestCnPed@x[vv3] } - utils::write.table(tds, file = fname, row.names = FALSE, col.names = FALSE, append = TRUE, sep = ",") + utils::write.table(tds, file = fname, row.names = FALSE, + col.names = FALSE, append = TRUE, sep = ",") } - if (!(j %% 500)) { + if (!(j %% update_rate)) { cat(paste0("Done with ", j, " of ", nc, "\n")) } } + return(NULL) } - - - - # Merge and write the parentage matrices - # df <- full_join(mat_ped_matrix %>% arrange(ID), pat_ped_matrix %>% arrange(ID)) - - # write.table(df, file = mapa_id_file, sep = ",", append = FALSE, row.names = FALSE) -} diff --git a/R/readGedcom.R b/R/readGedcom.R index 16251cef..d6858e58 100644 --- a/R/readGedcom.R +++ b/R/readGedcom.R @@ -350,6 +350,7 @@ readGedcom <- function(file_path, #' @param df_temp A data frame containing information about individuals. #' @return A list mapping family IDs to parent IDs. #' @keywords internal +#' mapFAMS2parents <- function(df_temp) { if (!all(c("FAMS", "sex") %in% colnames(df_temp))) { warning("The data frame does not contain the necessary columns (FAMS, sex)") @@ -544,6 +545,7 @@ countPatternRows <- function(file) { #' @param vars The current list of variables to update. #' @return A list with updated `vars` and a `matched` flag. #' @keywords internal +#' process_tag <- function(tag, field_name, pattern_rows, line, vars, extractor = NULL, mode = "replace") { count_name <- paste0("num_", tolower(tag), "_rows") diff --git a/R/summarizePedigree.R b/R/summarizePedigree.R index 1fbd06ac..11a3e7e4 100644 --- a/R/summarizePedigree.R +++ b/R/summarizePedigree.R @@ -104,14 +104,13 @@ summarizePedigrees <- function(ped, famID = "famID", personID = "ID", if (network_checks) { if (verbose) message("Performing network validation checks...") - network_validation_results <- checkPedigreeNetwork( + output$network_validation <- checkPedigreeNetwork( ped, personID = personID, momID = momID, dadID = dadID, verbose = verbose ) - output$network_validation <- network_validation_results } # Calculate summary statistics for families, maternal lines, and paternal lines @@ -191,50 +190,59 @@ summarizePedigrees <- function(ped, famID = "famID", personID = "ID", # repair = FALSE, verbose = verbose) # } - - # Optionally find the superlative lines - # & noldest <= unique(ped_dt[[famID]]) - # determin number of lines - - ## oldest if (!is.null(byr) && noldest > 0) { if (!is.null(n_families) && "families" %in% type) { if (verbose) message("Finding oldest families...") - output$oldest_families <- try_na(family_summary_dt[order(get(byr))][1:min(c(noldest, n_families), - na.rm = TRUE - )]) + output$oldest_families <- findOldest( + foo_summary_dt = family_summary_dt, + byr = byr, + noldest = noldest, + n_foo = n_families + ) } if (!is.null(n_mothers) && "mothers" %in% type) { if (verbose) message("Finding oldest maternal lines...") - output$oldest_maternal <- try_na(maternal_summary_dt[order(get(byr))][1:min(c(noldest, n_mothers), - na.rm = TRUE - )]) + output$oldest_maternal <- findOldest( + foo_summary_dt = maternal_summary_dt, + byr = byr, + noldest = noldest, + n_foo = n_mothers + ) } if (!is.null(n_fathers) && "fathers" %in% type) { if (verbose) message("Finding oldest paternal lines...") - output$oldest_paternal <- try_na(paternal_summary_dt[order(get(byr))][1:min(c(noldest, n_fathers), - na.rm = TRUE - )]) + output$oldest_paternal <- findOldest( + foo_summary_dt = paternal_summary_dt, + byr = byr, + noldest = noldest, + n_foo = n_fathers + ) } } # biggest lines if (!is.null(nbiggest) && nbiggest > 0) { if (!is.null(n_families) && "families" %in% type) { - output$biggest_families <- try_na(family_summary_dt[order(-get("count"))][1:min(c(nbiggest, n_families), - na.rm = TRUE - )]) + output$biggest_families <- findBiggest( + foo_summary_dt = family_summary_dt, + nbiggest = nbiggest, + n_foo = n_families + ) } if (!is.null(n_mothers) && "mothers" %in% type) { - output$biggest_maternal <- try_na(maternal_summary_dt[order(-get("count"))][1:min(c(nbiggest, n_mothers), - na.rm = TRUE - )]) + output$biggest_maternal <- findBiggest( + foo_summary_dt = maternal_summary_dt, + nbiggest = nbiggest, + n_foo = n_mothers + ) } if (!is.null(n_fathers) && "fathers" %in% type) { - output$biggest_paternal <- try_na(paternal_summary_dt[order(-get("count"))][1:min(c(nbiggest, n_fathers), - na.rm = TRUE - )]) + output$biggest_paternal <- findBiggest( + foo_summary_dt = paternal_summary_dt, + nbiggest = nbiggest, + n_foo = n_fathers + ) } } @@ -385,3 +393,29 @@ summarizeFamilies <- function(ped, famID = "famID", personID = "ID", founder_sort_var = founder_sort_var ) } +# Function to find the oldest individuals in a pedigree +#' This function finds the oldest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function. +#' @inheritParams summarizePedigrees +#' @param foo_summary_dt A data.table containing the summary statistics. +#' @param n_foo An integer specifying the number of individuals in the summary. +#' @returns a data.table containing the oldest families in the pedigree. + +findOldest <- function(foo_summary_dt, byr, noldest, n_foo) { + oldest_foo <- try_na(foo_summary_dt[order(get(byr))][1:min(c(noldest, n_foo), + na.rm = TRUE + )]) + return(oldest_foo) +} + +# Function to find the biggest families in a pedigree +#' This function finds the biggest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function. +#' @inheritParams findOldest +#' @returns a data.table containing the biggest families in the pedigree. + + +findBiggest <- function(foo_summary_dt, nbiggest, n_foo) { + biggest_foo <- try_na(foo_summary_dt[order(-get("count"))][1:min(c(nbiggest, n_foo), + na.rm = TRUE + )]) + return(biggest_foo) +} diff --git a/data-raw/benchmark.R b/data-raw/benchmark.R index 80d6db40..8c4c90b5 100644 --- a/data-raw/benchmark.R +++ b/data-raw/benchmark.R @@ -59,4 +59,5 @@ benchmark_results <- microbenchmark( print(benchmark_results) # Optional: Save results to CSV for later analysis -write.csv(summary(benchmark_results), "benchmark_results.csv", row.names = FALSE) +write.csv(summary(benchmark_results), + "benchmark_results.csv", row.names = FALSE) diff --git a/data-raw/df_ASOIAF.R b/data-raw/df_ASOIAF.R index 2874d8a8..9741d04f 100644 --- a/data-raw/df_ASOIAF.R +++ b/data-raw/df_ASOIAF.R @@ -29,7 +29,8 @@ df <- ped2fam(ASOIAF, personID = "id") %>% name = str_remove(name, "/") ) -# pedADD <- ped2com(df , personID = "id", momID = "momID", dadID = "dadID", component = "additive", isChild_method = "partial_parent") +# pedADD <- ped2com(df , personID = "id", momID = "momID", +# dadID = "dadID", component = "additive", isChild_method = "partial_parent") # com2links(ad_ped_matrix=pedADD) # if missing momID or dadID, assign the next available ID diff --git a/data-raw/df_inbreeding.R b/data-raw/df_inbreeding.R index 4419baef..9af10ff0 100644 --- a/data-raw/df_inbreeding.R +++ b/data-raw/df_inbreeding.R @@ -8,5 +8,5 @@ inbreeding <- raw ## # data processing -write.csv(inbreeding, "data-raw/inbreeding.csv", row.names = FALSE) +#write.csv(inbreeding, "data-raw/inbreeding.csv", row.names = FALSE) usethis::use_data(inbreeding, overwrite = TRUE, compress = "xz") diff --git a/data-raw/df_potter.R b/data-raw/df_potter.R index 153df4e4..aa039c81 100644 --- a/data-raw/df_potter.R +++ b/data-raw/df_potter.R @@ -45,7 +45,9 @@ potter <- data.frame( "Molly Weasley", "Lucy Weasley" ), - gen = c(1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3), + gen = c(1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3), momID = c( 101, 101, 103, 103, NA, 3, 4, 10, NA, NA, 10, 10, 10, 10, 10, 10, NA, 105, 105, NA, @@ -137,7 +139,8 @@ potter[nrow(potter) + 1, ] <- list( 0 ) -# potter[nrow(potter) + 1,] <- list(personID,fam,name,gen,momID,dadID,spouseID,sex) +# potter[nrow(potter) + 1,] <- list(personID,fam,name,gen, +# momID,dadID,spouseID,sex) write_csv(potter, here("data-raw", "potter.csv")) usethis::use_data(potter, overwrite = TRUE, compress = "xz") diff --git a/man/findBiggest.Rd b/man/findBiggest.Rd new file mode 100644 index 00000000..82d58e70 --- /dev/null +++ b/man/findBiggest.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/summarizePedigree.R +\name{findBiggest} +\alias{findBiggest} +\title{This function finds the biggest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function.} +\usage{ +findBiggest(foo_summary_dt, nbiggest, n_foo) +} +\arguments{ +\item{foo_summary_dt}{A data.table containing the summary statistics.} + +\item{nbiggest}{An integer specifying the number of biggest families to find.} + +\item{n_foo}{An integer specifying the number of individuals in the summary.} +} +\description{ +This function finds the biggest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function. +} diff --git a/man/findOldest.Rd b/man/findOldest.Rd new file mode 100644 index 00000000..8b5caf27 --- /dev/null +++ b/man/findOldest.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/summarizePedigree.R +\name{findOldest} +\alias{findOldest} +\title{This function finds the oldest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function.} +\usage{ +findOldest(foo_summary_dt, byr, noldest, n_foo) +} +\arguments{ +\item{foo_summary_dt}{A data.table containing the summary statistics.} + +\item{byr}{Character. Optional column name for birth year. Used to determine the oldest lineages.} + +\item{noldest}{Integer. Number of oldest lineages to return (sorted by birth year).} + +\item{n_foo}{An integer specifying the number of individuals in the summary.} +} +\description{ +This function finds the oldest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function. +} diff --git a/man/isChild.Rd b/man/isChild.Rd new file mode 100644 index 00000000..1bd6f1e9 --- /dev/null +++ b/man/isChild.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/convertPedigree.R +\name{isChild} +\alias{isChild} +\title{Determine isChild Status, isChild is the 'S' matrix from RAM} +\usage{ +isChild(isChild_method, ped) +} +\arguments{ +\item{isChild_method}{method to determine isChild status} + +\item{ped}{pedigree data frame} +} +\value{ +isChild 'S' matrix +} +\description{ +Determine isChild Status, isChild is the 'S' matrix from RAM +} diff --git a/man/ped2fam.Rd b/man/ped2fam.Rd index 4dbc9aa5..2ff7eb0d 100644 --- a/man/ped2fam.Rd +++ b/man/ped2fam.Rd @@ -30,7 +30,8 @@ ped2fam( A pedigree dataset with one additional column for the newly created extended family ID } \description{ -This function adds an extended family ID variable to a pedigree by segmenting that dataset into independent extended families +This function adds an extended family ID variable to a pedigree by segmenting +that dataset into independent extended families using the weakly connected components algorithm. } \details{ diff --git a/man/ped2maternal.Rd b/man/ped2maternal.Rd index 88106fb6..e54c3e76 100755 --- a/man/ped2maternal.Rd +++ b/man/ped2maternal.Rd @@ -22,7 +22,7 @@ ped2maternal( \item{dadID}{character. Name of the column in ped for the father ID variable} -\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} +\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} \item{...}{additional arguments to be passed to \code{\link{ped2com}}} } diff --git a/man/summarizeFamilies.Rd b/man/summarizeFamilies.Rd index 54fc978d..8903eb93 100644 --- a/man/summarizeFamilies.Rd +++ b/man/summarizeFamilies.Rd @@ -33,7 +33,7 @@ summarizeFamilies( \item{dadID}{character. Name of the column in ped for the father ID variable} -\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} +\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} \item{patID}{Character. Paternal line ID variable to be created and added to the pedigree} diff --git a/man/summarizeMatrilines.Rd b/man/summarizeMatrilines.Rd index 1930858a..2890b622 100644 --- a/man/summarizeMatrilines.Rd +++ b/man/summarizeMatrilines.Rd @@ -33,7 +33,7 @@ summarizeMatrilines( \item{dadID}{character. Name of the column in ped for the father ID variable} -\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} +\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} \item{patID}{Character. Paternal line ID variable to be created and added to the pedigree} diff --git a/man/summarizePatrilines.Rd b/man/summarizePatrilines.Rd index 846b0e4b..aed89bcd 100644 --- a/man/summarizePatrilines.Rd +++ b/man/summarizePatrilines.Rd @@ -33,7 +33,7 @@ summarizePatrilines( \item{dadID}{character. Name of the column in ped for the father ID variable} -\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} +\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} \item{patID}{Character. Paternal line ID variable to be created and added to the pedigree} diff --git a/man/summarizePedigrees.Rd b/man/summarizePedigrees.Rd index 1a076fdc..b298d326 100644 --- a/man/summarizePedigrees.Rd +++ b/man/summarizePedigrees.Rd @@ -35,7 +35,7 @@ summarizePedigrees( \item{dadID}{character. Name of the column in ped for the father ID variable} -\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} +\item{matID}{Character. Maternal line ID variable to be created and added to the pedigree} \item{patID}{Character. Paternal line ID variable to be created and added to the pedigree} diff --git a/tests/testthat/test-computeRelatedness.R b/tests/testthat/test-computeRelatedness.R index 10b0de29..05fb7e62 100644 --- a/tests/testthat/test-computeRelatedness.R +++ b/tests/testthat/test-computeRelatedness.R @@ -25,6 +25,15 @@ test_that("calculateRelatedness function with empirical", { }) +# Test 7: empirical divide by zero + +test_that("calculateH handles divide by zero for empirical", { +expect_error( + calculateRelatedness(generations = 2, + empirical = TRUE, total_a = 0, + total_m = 0)) + +}) test_that("inferRelatedness performs as expected", { result <- inferRelatedness(0, aceA = .9, aceC = 0, sharedC = 0) expect_equal(result, 0) @@ -96,3 +105,4 @@ test_that("calculateH stops for illegal coefficients", { "The observed correlations should be between -1 and 1" ) }) + diff --git a/tests/testthat/test-summarizePedigrees.R b/tests/testthat/test-summarizePedigrees.R index 13cb46e0..61dae665 100644 --- a/tests/testthat/test-summarizePedigrees.R +++ b/tests/testthat/test-summarizePedigrees.R @@ -51,17 +51,22 @@ test_that("summarizeFamilies() works with additional summary stats", { # Test Case 4: Does this function work for summarizeMatrilines test_that("summarizeMatrilines() works", { nbiggest <- 2 - df <- ped2fam(potter, famID = "newFamID", personID = "personID") %>% ped2maternal(personID = "personID") - df_summarized <- summarizeMatrilines(df, famID = "newFamID", personID = "personID", nbiggest = nbiggest) + df <- ped2fam(potter, famID = "newFamID", personID = "personID") %>% + ped2maternal(personID = "personID") + df_summarized <- summarizeMatrilines(df, famID = "newFamID", + personID = "personID", + nbiggest = nbiggest) # is the total count from the family summary the same as the raw data? result_observed <- sum(df_summarized$maternal_summary$count) result_expected <- nrow(potter) expect_equal(result_observed, result_expected) - # is the count of the summarized data frame equal to the number of unique families in the input data frame? + # is the count of the summarized data frame equal to the number of + # unique families in the input data frame? result_observed <- length(df_summarized$maternal_summary$count) result_expected <- length(unique(df$matID)) expect_equal(result_observed, result_expected) - # is the count of the biggest families equal to the number of unique families in the input data frame? + # is the count of the biggest families equal to the number of + # unique families in the input data frame? result_observed <- nrow(df_summarized$biggest_maternal) expect_equal(result_observed, nbiggest) }) @@ -69,17 +74,22 @@ test_that("summarizeMatrilines() works", { # Test Case 5: Does this function work for summarizePatrilines test_that("summarizePatrilines() works", { nbiggest <- 4 - df <- ped2fam(potter, famID = "newFamID", personID = "personID") %>% ped2paternal(personID = "personID") - df_summarized <- summarizePatrilines(df, famID = "newFamID", personID = "personID", nbiggest = nbiggest) + df <- ped2fam(potter, famID = "newFamID", personID = "personID") %>% + ped2paternal(personID = "personID") + df_summarized <- summarizePatrilines(df, famID = "newFamID", + personID = "personID", + nbiggest = nbiggest) # is the total count from the family summary the same as the raw data? result_observed <- sum(df_summarized$paternal_summary$count) result_expected <- nrow(potter) expect_equal(result_observed, result_expected) - # is the count of the summarized data frame equal to the number of unique families in the input data frame? + # is the count of the summarized data frame equal to the number of + # unique families in the input data frame? result_observed <- length(df_summarized$paternal_summary$count) result_expected <- length(unique(df$patID)) expect_equal(result_observed, result_expected) - # is the count of the biggest families equal to the number of unique families in the input data frame? + # is the count of the biggest families equal to the number of + # unique families in the input data frame? result_observed <- nrow(df_summarized$biggest_paternal) expect_equal(result_observed, nbiggest) }) @@ -101,7 +111,7 @@ test_that("summarizePedigrees() handles missing values correctly", { }) # Test Case 7: When all variables are skipped -test_that("summarizePedigrees() works when all numeric variables are skipped", { +test_that("summarizePedigrees works when all numeric variables are skipped",{ df <- data.frame( ID = 1:5, momID = c(NA, 1, 1, NA, 4), @@ -116,7 +126,8 @@ test_that("summarizePedigrees() works when all numeric variables are skipped", { # Test Case 8: Handling invalid column names test_that("summarizePedigrees() throws error on invalid column names", { - df <- data.frame(ID = 1:5, momID = c(NA, 1, 1, NA, 4), dadID = c(NA, 2, 2, NA, 5), famID = c(1, 1, 1, 2, 2)) + df <- data.frame(ID = 1:5, momID = c(NA, 1, 1, NA, 4), + dadID = c(NA, 2, 2, NA, 5), famID = c(1, 1, 1, 2, 2)) expect_error(summarizePedigrees(df, byr = "unknown_column")) }) From 9d9ac4fe313814880b950ea9e5f88c403b6b9339 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 22:17:18 -0400 Subject: [PATCH 09/11] * reduced complexity of com2links and summarizePedigree with the use of subfunctions --- NEWS.md | 1 + R/makeLinks.R | 103 +++++++++++++++-------------- man/com2links.legacy.Rd | 21 ++++++ man/findBiggest.Rd | 5 +- man/findOldest.Rd | 3 + man/validate_and_convert_matrix.Rd | 28 ++++++++ tests/testthat/test-makeLinks.R | 5 +- 7 files changed, 110 insertions(+), 56 deletions(-) create mode 100644 man/com2links.legacy.Rd create mode 100644 man/validate_and_convert_matrix.Rd diff --git a/NEWS.md b/NEWS.md index f0fd33f4..d8bd3dd0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ * revived checkParents function to check for handling phantom parents and missing parents * added tests for checkParents function * added GoT analysis +* reduced complexity of com2links and summarizePedigree with the use of subfunctions # BGmisc 1.3.5.1 * Setting the default for the `sparse` argument in `ped2com()` to TRUE diff --git a/R/makeLinks.R b/R/makeLinks.R index fe40bc59..91b6ea1e 100644 --- a/R/makeLinks.R +++ b/R/makeLinks.R @@ -46,42 +46,27 @@ com2links <- function( # Ensure that at least one relationship matrix is provided. if (is.null(ad_ped_matrix) && is.null(mit_ped_matrix) && is.null(cn_ped_matrix)) { - stop("At least one of 'ped_matrix', 'mit_ped_matrix', or 'cn_ped_matrix' must be provided.") + stop("At least one of 'ad_ped_matrix', 'mit_ped_matrix', or 'cn_ped_matrix' must be provided.") } # Validate and convert ad_ped_matrix to a sparse dgCMatrix if provided. if (!is.null(ad_ped_matrix)) { - if (!inherits(ad_ped_matrix, c("matrix", "dgCMatrix", "dsCMatrix"))) { - stop("The 'ad_ped_matrix' must be a matrix or dgCMatrix.") - } - # convert to sparse - if (!inherits(ad_ped_matrix, "dgCMatrix")) { - ad_ped_matrix <- methods::as(ad_ped_matrix, "dgCMatrix") - } + ad_ped_matrix <- validate_and_convert_matrix(mat=ad_ped_matrix, + name = "ad_ped_matrix") } # Validate and convert cn_ped_matrix to a sparse dgCMatrix if provided. if (!is.null(cn_ped_matrix)) { - if (!inherits(cn_ped_matrix, c("matrix", "dgCMatrix", "dsCMatrix"))) { - stop("The 'cn_ped_matrix' must be a matrix or dgCMatrix.") - } - # convert to sparse - if (!inherits(cn_ped_matrix, "dgCMatrix")) { - cn_ped_matrix <- methods::as(cn_ped_matrix, "dgCMatrix") - } - # Ensure CN matrix is symmetric. - cn_ped_matrix <- methods::as(cn_ped_matrix, "symmetricMatrix") + cn_ped_matrix <- validate_and_convert_matrix(mat=cn_ped_matrix, + name="cn_ped_matrix", + ensure_symmetric = TRUE) } # Validate and process mit_ped_matrix: convert and ensure binary values. if (!is.null(mit_ped_matrix)) { - if (!inherits(mit_ped_matrix, c("matrix", "dgCMatrix", "dsCMatrix"))) { - stop("The 'mit_ped_matrix' must be a matrix or dgCMatrix.") - } - if (!inherits(mit_ped_matrix, "dgCMatrix")) { - mit_ped_matrix <- methods::as(mit_ped_matrix, "symmetricMatrix") - } - # Ensure mitochondrial matrix values are binary (0/1) - mit_ped_matrix@x[mit_ped_matrix@x > 0] <- 1 + + mit_ped_matrix <- validate_and_convert_matrix(mat=mit_ped_matrix, + name="mit_ped_matrix",force_binary = TRUE, + ensure_symmetric = TRUE) } # --- Build IDs and Prepare Matrix Pointers --- @@ -162,12 +147,7 @@ com2links <- function( # mapa_id_file <- paste0(outcome_name, "_data_mapaID.csv") # Initialize the related pairs file with headers. - df_relpairs <- data.frame( - ID1 = numeric(0), ID2 = numeric(0) - ) - df_relpairs[[relNames[1]]] <- numeric(0) - df_relpairs[[relNames[2]]] <- numeric(0) - df_relpairs[[relNames[3]]] <- numeric(0) + df_relpairs <- initialize_empty_df(relNames = relNames) # Write the headers to the related pairs file. if (writetodisk == TRUE) { @@ -261,10 +241,8 @@ com2links <- function( } } } - if (verbose) { - if (!(j %% update_rate)) { - cat(paste0("Done with ", j, " of ", nc, "\n")) - } + if (verbose && (j %% update_rate == 0L)) { + cat("Done with", j, "of", nc, "\n") } } } else if (sum_nulls == 2) { @@ -309,11 +287,8 @@ com2links <- function( } # Initialize the related pairs file with the appropriate headers. - df_relpairs <- data.frame( - ID1 = numeric(0), ID2 = numeric(0) - ) - df_relpairs[[relNames[1]]] <- numeric(0) - df_relpairs[[relNames[2]]] <- numeric(0) + df_relpairs <- initialize_empty_df(relNames = relNames) + if (writetodisk == TRUE) { utils::write.table( df_relpairs, @@ -386,10 +361,8 @@ com2links <- function( } } } - if (verbose) { - if (!(j %% update_rate)) { - cat(paste0("Done with ", j, " of ", nc, "\n")) - } + if (verbose && (j %% update_rate == 0L)) { + cat("Done with", j, "of", nc, "\n") } } } else if (sum_nulls == 1) { @@ -426,10 +399,8 @@ com2links <- function( } # Initialize the related pairs file. - df_relpairs <- data.frame( - ID1 = numeric(0), ID2 = numeric(0) - ) - df_relpairs[[relNames[1]]] <- numeric(0) + df_relpairs <- initialize_empty_df(relNames = relNames) + if (writetodisk == TRUE) { utils::write.table( df_relpairs, @@ -486,8 +457,8 @@ com2links <- function( } } } - if (verbose && !(j %% update_rate)) { - cat(paste0("Done with ", j, " of ", nc, "\n")) + if (verbose && (j %% update_rate == 0L)) { + cat("Done with", j, "of", nc, "\n") } } } else { @@ -510,7 +481,6 @@ com2links <- function( } else if (legacy) { # --- Legacy Mode --- # In legacy mode, convert matrices to the expected symmetric formats. - com2links.legacy( rel_pairs_file = rel_pairs_file, ad_ped_matrix = ad_ped_matrix, @@ -649,3 +619,34 @@ com2links.legacy <- function( } return(NULL) } + +#' @title validate_and_convert_matrix +#' @description +#' This function validates and converts a matrix to a specific format. +#' +#' @param mat The matrix to be validated and converted. +#' @param name The name of the matrix for error messages. +#' @param ensure_symmetric Logical indicating whether to ensure the matrix is symmetric. +#' @param force_binary Logical indicating whether to force the matrix to be binary. +#' +#' @return The validated and converted matrix. +validate_and_convert_matrix <- function(mat, name, ensure_symmetric = FALSE, force_binary = FALSE) { + if (!inherits(mat, c("matrix", "dgCMatrix", "dsCMatrix"))) { + stop(paste0("The '", name, "' must be a matrix or dgCMatrix.")) + } + if (!inherits(mat, "dgCMatrix")) { + mat <- methods::as(mat, if (ensure_symmetric) "symmetricMatrix" else "dgCMatrix") + } + if (force_binary) { + mat@x[mat@x > 0] <- 1 + } + return(mat) +} + +initialize_empty_df <- function(relNames) { + df <- data.frame(ID1 = numeric(0), ID2 = numeric(0)) + for (r in relNames) { + df[[r]] <- numeric(0) + } + return(df) +} diff --git a/man/com2links.legacy.Rd b/man/com2links.legacy.Rd new file mode 100644 index 00000000..d409e8ef --- /dev/null +++ b/man/com2links.legacy.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/makeLinks.R +\name{com2links.legacy} +\alias{com2links.legacy} +\title{com2links} +\usage{ +com2links.legacy( + rel_pairs_file = "dataRelatedPairs.csv", + ad_ped_matrix = NULL, + mit_ped_matrix = mt_ped_matrix, + mt_ped_matrix = NULL, + cn_ped_matrix = NULL, + update_rate = 500, + verbose = FALSE, + outcome_name = "data", + ... +) +} +\description{ +This legacy function converts pedigree matrices into a related pairs file. +} diff --git a/man/findBiggest.Rd b/man/findBiggest.Rd index 82d58e70..22c13d96 100644 --- a/man/findBiggest.Rd +++ b/man/findBiggest.Rd @@ -9,10 +9,11 @@ findBiggest(foo_summary_dt, nbiggest, n_foo) \arguments{ \item{foo_summary_dt}{A data.table containing the summary statistics.} -\item{nbiggest}{An integer specifying the number of biggest families to find.} - \item{n_foo}{An integer specifying the number of individuals in the summary.} } +\value{ +a data.table containing the biggest families in the pedigree. +} \description{ This function finds the biggest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function. } diff --git a/man/findOldest.Rd b/man/findOldest.Rd index 8b5caf27..f97fa65c 100644 --- a/man/findOldest.Rd +++ b/man/findOldest.Rd @@ -15,6 +15,9 @@ findOldest(foo_summary_dt, byr, noldest, n_foo) \item{n_foo}{An integer specifying the number of individuals in the summary.} } +\value{ +a data.table containing the oldest families in the pedigree. +} \description{ This function finds the oldest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function. } diff --git a/man/validate_and_convert_matrix.Rd b/man/validate_and_convert_matrix.Rd new file mode 100644 index 00000000..f6bf5c2a --- /dev/null +++ b/man/validate_and_convert_matrix.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/makeLinks.R +\name{validate_and_convert_matrix} +\alias{validate_and_convert_matrix} +\title{validate_and_convert_matrix} +\usage{ +validate_and_convert_matrix( + mat, + name, + ensure_symmetric = FALSE, + force_binary = FALSE +) +} +\arguments{ +\item{mat}{The matrix to be validated and converted.} + +\item{name}{The name of the matrix for error messages.} + +\item{ensure_symmetric}{Logical indicating whether to ensure the matrix is symmetric.} + +\item{force_binary}{Logical indicating whether to force the matrix to be binary.} +} +\value{ +The validated and converted matrix. +} +\description{ +This function validates and converts a matrix to a specific format. +} diff --git a/tests/testthat/test-makeLinks.R b/tests/testthat/test-makeLinks.R index 2edb8377..e8dfe99e 100644 --- a/tests/testthat/test-makeLinks.R +++ b/tests/testthat/test-makeLinks.R @@ -1,7 +1,7 @@ test_that("com2links handles missing matrices properly", { expect_error( com2links(ad_ped_matrix = NULL, mit_ped_matrix = NULL, cn_ped_matrix = NULL), - "At least one of 'ped_matrix', 'mit_ped_matrix', or 'cn_ped_matrix' must be provided." + "At least one of 'ad_ped_matrix', 'mit_ped_matrix', or 'cn_ped_matrix' must be provided." ) }) @@ -131,8 +131,7 @@ test_that("com2links correctly handles missing matrices", { expect_error( com2links(ad_ped_matrix = NULL, mit_ped_matrix = NULL, cn_ped_matrix = NULL), - "At least one of 'ped_matrix', 'mit_ped_matrix', or 'cn_ped_matrix' must be provided." - ) + "At least one of 'ad_ped_matrix', 'mit_ped_matrix', or 'cn_ped_matrix' must be provided." ) expect_error(com2links(ad_ped_matrix = hazard), "The 'ad_ped_matrix' must be a matrix or dgCMatrix.") }) From 837b97b9d476c192a437c6d853dcb27841c96e6f Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 22:52:18 -0400 Subject: [PATCH 10/11] missed documentation --- DESCRIPTION | 2 +- R/makeLinks.R | 13 +++++++++++-- R/summarizePedigree.R | 1 + man/com2links.legacy.Rd | 21 ++++++++++++++++++++- man/findBiggest.Rd | 2 ++ man/initialize_empty_df.Rd | 18 ++++++++++++++++++ 6 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 man/initialize_empty_df.Rd diff --git a/DESCRIPTION b/DESCRIPTION index b35bfadd..bcdab09f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: BGmisc Title: An R Package for Extended Behavior Genetics Analysis -Version: 1.3.5.1 +Version: 1.3.6 Authors@R: c( person("S. Mason", "Garrison", , "garrissm@wfu.edu", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-4804-6003")), diff --git a/R/makeLinks.R b/R/makeLinks.R index 91b6ea1e..96768fd5 100644 --- a/R/makeLinks.R +++ b/R/makeLinks.R @@ -501,10 +501,10 @@ com2links <- function( # write.table(df, file = mapa_id_file, sep = ",", append = FALSE, row.names = FALSE) } -#' @title com2links +#' Convert Pedigree Matrices to Related Pairs File (Legacy) #' @description #' This legacy function converts pedigree matrices into a related pairs file. - +#' @inheritParams com2links com2links.legacy <- function( rel_pairs_file = "dataRelatedPairs.csv", @@ -643,6 +643,15 @@ validate_and_convert_matrix <- function(mat, name, ensure_symmetric = FALSE, for return(mat) } +#' @title initialize_empty_df +#' @description +#' This function initializes an empty data frame with specified column names. +#' +#' @param relNames A vector of column names to be included in the data frame. +#' +#' @return An empty data frame with specified column names. +#' @keywords internal + initialize_empty_df <- function(relNames) { df <- data.frame(ID1 = numeric(0), ID2 = numeric(0)) for (r in relNames) { diff --git a/R/summarizePedigree.R b/R/summarizePedigree.R index 11a3e7e4..6c6564f3 100644 --- a/R/summarizePedigree.R +++ b/R/summarizePedigree.R @@ -410,6 +410,7 @@ findOldest <- function(foo_summary_dt, byr, noldest, n_foo) { # Function to find the biggest families in a pedigree #' This function finds the biggest families in a pedigree. It is supposed to be used internally by the \code{summarize_pedigree} function. #' @inheritParams findOldest +#' @inheritParams summarizePedigrees #' @returns a data.table containing the biggest families in the pedigree. diff --git a/man/com2links.legacy.Rd b/man/com2links.legacy.Rd index d409e8ef..757117a1 100644 --- a/man/com2links.legacy.Rd +++ b/man/com2links.legacy.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/makeLinks.R \name{com2links.legacy} \alias{com2links.legacy} -\title{com2links} +\title{Convert Pedigree Matrices to Related Pairs File (Legacy)} \usage{ com2links.legacy( rel_pairs_file = "dataRelatedPairs.csv", @@ -16,6 +16,25 @@ com2links.legacy( ... ) } +\arguments{ +\item{rel_pairs_file}{File path to write related pairs to (CSV format).} + +\item{ad_ped_matrix}{Matrix of additive genetic relatedness coefficients.} + +\item{mit_ped_matrix}{Matrix of mitochondrial relatedness coefficients. Alias: \code{mt_ped_matrix}.} + +\item{mt_ped_matrix}{Matrix of mitochondrial relatedness coefficients.} + +\item{cn_ped_matrix}{Matrix of common nuclear relatedness coefficients.} + +\item{update_rate}{Numeric. Frequency (in iterations) at which progress messages are printed.} + +\item{verbose}{Logical. If TRUE, prints progress messages.} + +\item{outcome_name}{Character string representing the outcome name (used in file naming).} + +\item{...}{Additional arguments to be passed to \code{\link{com2links}}} +} \description{ This legacy function converts pedigree matrices into a related pairs file. } diff --git a/man/findBiggest.Rd b/man/findBiggest.Rd index 22c13d96..c3b3d23e 100644 --- a/man/findBiggest.Rd +++ b/man/findBiggest.Rd @@ -9,6 +9,8 @@ findBiggest(foo_summary_dt, nbiggest, n_foo) \arguments{ \item{foo_summary_dt}{A data.table containing the summary statistics.} +\item{nbiggest}{Integer. Number of largest lineages to return (sorted by count).} + \item{n_foo}{An integer specifying the number of individuals in the summary.} } \value{ diff --git a/man/initialize_empty_df.Rd b/man/initialize_empty_df.Rd new file mode 100644 index 00000000..637d83cb --- /dev/null +++ b/man/initialize_empty_df.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/makeLinks.R +\name{initialize_empty_df} +\alias{initialize_empty_df} +\title{initialize_empty_df} +\usage{ +initialize_empty_df(relNames) +} +\arguments{ +\item{relNames}{A vector of column names to be included in the data frame.} +} +\value{ +An empty data frame with specified column names. +} +\description{ +This function initializes an empty data frame with specified column names. +} +\keyword{internal} From 26c0eb0aa2d3b2c0d4af606f11fe37b362f06318 Mon Sep 17 00:00:00 2001 From: Mason Garrison Date: Mon, 7 Apr 2025 22:55:28 -0400 Subject: [PATCH 11/11] comment out skipped test --- tests/testthat/test-readPedigrees.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-readPedigrees.R b/tests/testthat/test-readPedigrees.R index 349b4f30..1f098697 100644 --- a/tests/testthat/test-readPedigrees.R +++ b/tests/testthat/test-readPedigrees.R @@ -214,10 +214,10 @@ test_that("readWikifamilytree reads a string correctly", { # read E:/Dropbox/Lab/Research/Projects/2024/BGMiscJoss/BGmisc_main/data-raw/Targaryen tree Dance.txt -test_that("readWikifamilytree reads a file correctly", { +#test_that("readWikifamilytree reads a file correctly", { # Create a temporary WikiFamilyTree file for testing # Example usage - family_tree_file_path <- "data-raw/Targaryen tree Dance.txt" # system.file("extdata", "Targaryen tree Dance.txt", package = "BGmisc") +# family_tree_file_path <- "data-raw/Targaryen tree Dance.txt" # system.file("extdata", "Targaryen tree Dance.txt", package = "BGmisc") # result <- readWikifamilytree(file_path=family_tree_file_path) -}) +#})
library(BGmisc)
 library(tidyverse)