Interactive Hierarchical Taxonomic Tree Visualization

Author

Ben - EcoQuants

Published

2025-10-30 00:00:00

Show code
# Load required libraries
librarian::shelf(
  # collapsibleTree, d3treeR/d3treeR, 
  DT, glue, here, htmlwidgets, 
  # networkD3, plotly,
  yogevherz/plotme,
  tidyverse, 
  # treemap, 
  worrms)

# Set options
options(stringsAsFactors = FALSE)

1 Data Loading and Exploration

Show code
species_csv <- here("data/species_USA_2025-10-29.csv")

# Read the species data
df_species <- read_csv(species_csv) #|> 
  # filter(component == "other")

# Display data structure
glimpse(df_species)
Rows: 17,333
Columns: 14
$ component       <chr> "bird", "bird", "bird", "bird", "bird", "bird", "bird"…
$ taxon_authority <chr> "botw", "botw", "botw", "botw", "botw", "botw", "botw"…
$ taxon_id        <dbl> 22725044, 22695503, 22710921, 22714797, 103780078, 226…
$ taxon_str       <chr> "botw:22725044", "botw:22695503", "botw:22710921", "bo…
$ taxon_url       <chr> "https://birdsoftheworld.org", "https://birdsoftheworl…
$ scientific      <chr> "Acanthis flammea", "Accipiter soloensis", "Acridother…
$ common          <chr> "Redpoll", "Chinese Sparrowhawk", "Common Myna", "Mill…
$ rl_code         <chr> "LC", "LC", "LC", "EN", "EN", "LC", "LC", "LC", "EN", …
$ rl_score        <dbl> 0.2, 0.2, 0.2, 0.8, 0.8, 0.2, 0.2, 0.2, 0.8, 0.2, 0.2,…
$ model_id        <dbl> 17554, 17555, 17557, 18319, 18320, 17558, 17559, 17560…
$ model_url       <chr> "https://shiny.marinesensitivity.org/mapsp/?mdl_seq=17…
$ area_km2        <dbl> 2.866953e+02, 2.986610e+01, 4.028859e+02, 2.836896e+01…
$ avg_suit        <dbl> 0.5, 0.5, 0.5, 0.9, 0.9, 0.5, 0.5, 0.5, 0.9, 0.5, 0.5,…
$ pct_component   <dbl> 2.861063e-07, 2.980473e-08, 4.020581e-07, 2.038368e-07…
Show code
# Show unique components and taxon authorities
cat("Unique components:", paste(unique(df_species$component), collapse = ", "), "\n")
Unique components: bird, coral, fish, invertebrate, mammal, other, reptile 
Show code
cat("Unique taxon authorities:", paste(unique(df_species$taxon_authority), collapse = ", "), "\n")
Unique taxon authorities: botw, worms 
Show code
cat("Total records:", nrow(df_species), "\n")
Total records: 17333 
Show code
cat("WoRMS records:", sum(df_species$taxon_authority == "worms", na.rm = TRUE), "\n")
WoRMS records: 16873 
Show code
cat("BOTW records:", sum(df_species$taxon_authority == "botw", na.rm = TRUE), "\n")
BOTW records: 460 

2 Taxonomic Data Selection

Show code
# Choose which taxonomy to visualize - you can change this
taxonomy_source <- "worms"  # Change to "botw" for Birds of the World

# Filter data based on selected taxonomy
if (taxonomy_source == "worms") {
  filtered_df <- df_species %>%
    filter(taxon_authority == "worms") %>%
    mutate(taxon_id_num = as.numeric(taxon_id))
  
  cat("Selected WoRMS taxonomy with", nrow(filtered_df), "species\n")
} else if (taxonomy_source == "botw") {
  filtered_df <- df_species %>%
    filter(taxon_authority == "botw")
  
  cat("Selected Birds of the World taxonomy with", nrow(filtered_df), "species\n")
}
Selected WoRMS taxonomy with 16873 species
Show code
# Sample data for initial display
filtered_df %>%
  select(component, taxon_id, scientific, common) %>%
  head(10) %>%
  DT::datatable(options = list(pageLength = 5))

3 Fetch Taxonomic Hierarchy

Show code
# Function to get WoRMS classification with error handling
get_worms_classification <- function(aphia_id) {
  tryCatch({
    classification <- wm_classification(aphia_id)
    if (!is.null(classification) && nrow(classification) > 0) {
      return(classification)
    }
    return(NULL)
  }, error = function(e) {
    return(NULL)
  })
}

# Function to create simplified hierarchy for BOTW (birds)
create_bird_hierarchy <- function(df) {
  # For birds, we'll create a simplified hierarchy based on scientific names
  df %>%
    mutate(
      # Extract genus from scientific name
      genus = str_extract(scientific, "^[A-Z][a-z]+"),
      # Group by first letter for higher level
      order_group = paste0("Group_", substr(genus, 1, 1)),
      # Use component as kingdom
      kingdom = "Animalia",
      phylum = "Chordata",
      class = "Aves",
      order = order_group,
      family = paste0(genus, "_family"),
      species = scientific
    ) %>%
    select(taxon_id, kingdom, phylum, class, order, family, genus, species, scientific, common, component)
}

# Process based on taxonomy source
if (taxonomy_source == "worms") {
  # For WoRMS, fetch real taxonomic classification
  # Limit to first 50 species for demo (remove this limit for full analysis)
  sample_ids <- filtered_df %>%
    # slice_head(n = 50) %>%
    pull(taxon_id_num)

  # Define cache file path
  cache_file <- here("data/worms_classifications_cache.csv")

  # Load existing classifications from cache if available
  existing_classifications <- NULL
  processed_ids <- c()

  if (file.exists(cache_file)) {
    cat("Loading existing classifications from cache...\n")
    existing_classifications <- read_csv(cache_file, show_col_types = FALSE)
    processed_ids <- unique(existing_classifications$species_id)
    cat("Found", length(processed_ids), "previously processed species\n")
  }

  # Determine which IDs still need to be fetched
  remaining_ids <- setdiff(sample_ids, processed_ids)

  # Fetch classifications
  classifications <- list()

  if (length(remaining_ids) > 0) {
    cat("Fetching WoRMS classifications for", length(remaining_ids), "species...\n")
    pb <- txtProgressBar(min = 0, max = length(remaining_ids), style = 3)

    for (i in seq_along(remaining_ids)) {
      aphia_id <- remaining_ids[i]
      class_data <- get_worms_classification(aphia_id)

      if (!is.null(class_data)) {
        # Add the species info
        species_info <- filtered_df %>%
          filter(taxon_id_num == aphia_id) %>%
          slice(1)

        class_data$species_id <- aphia_id
        class_data$scientific_name <- species_info$scientific
        class_data$common_name <- species_info$common
        class_data$component <- species_info$component

        classifications[[length(classifications) + 1]] <- class_data

        # Save to cache every 10 species to minimize data loss
        if (length(classifications) %% 10 == 0) {
          new_data <- bind_rows(classifications)
          if (!is.null(existing_classifications)) {
            combined_data <- bind_rows(existing_classifications, new_data)
          } else {
            combined_data <- new_data
          }
          write_csv(combined_data, cache_file)
        }
      }

      setTxtProgressBar(pb, i)
    }
    close(pb)

    # Final save of any remaining classifications
    if (length(classifications) > 0) {
      new_data <- bind_rows(classifications)
      if (!is.null(existing_classifications)) {
        all_classifications_cached <- bind_rows(existing_classifications, new_data)
      } else {
        all_classifications_cached <- new_data
      }
      write_csv(all_classifications_cached, cache_file)
      cat("\nSaved", length(remaining_ids), "new classifications to cache\n")
    }
  } else {
    cat("All species already processed!\n")
  }
  
  # Combine all classifications (from cache and new fetches)
  if (!is.null(existing_classifications) || length(classifications) > 0) {
    if (length(classifications) > 0 && !is.null(existing_classifications)) {
      # Combine existing and new
      all_classifications <- bind_rows(existing_classifications, bind_rows(classifications))
    } else if (length(classifications) > 0) {
      # Only new classifications
      all_classifications <- bind_rows(classifications)
    } else {
      # Only existing from cache
      all_classifications <- existing_classifications
    }

    # Create hierarchical structure
    hierarchy_df <- all_classifications %>%
      select(species_id, rank, scientificname, scientific_name, common_name, component) %>%
      pivot_wider(
        id_cols = c(species_id, scientific_name, common_name, component),
        names_from = rank,
        values_from = scientificname,
        values_fill = NA
      )
  } else {
    # Fallback if no classifications fetched
    hierarchy_df <- filtered_df %>%
      slice_head(n = 50) %>%
      mutate(
        Kingdom = "Marine Life",
        Phylum = component,
        Class = paste0(component, "_class"),
        Order = substr(scientific, 1, 3),
        Family = str_extract(scientific, "^[A-Z][a-z]+"),
        Species = scientific
      ) %>%
      select(taxon_id, Kingdom, Phylum, Class, Order, Family, Species, scientific, common, component)
  }
  
} else {
  # For BOTW, use simplified hierarchy
  hierarchy_df <- create_bird_hierarchy(filtered_df %>% slice_head(n = 100))
}
Loading existing classifications from cache...
Found 16873 previously processed species
All species already processed!
Show code
# Display hierarchy structure
cat("\nHierarchy structure:\n")

Hierarchy structure:
Show code
glimpse(hierarchy_df)
Rows: 16,873
Columns: 36
$ species_id                <dbl> 125371, 430639, 207384, 288878, 888170, 7669…
$ scientific_name           <chr> "Acanella arbuscula", "Acanthastrea brevis",…
$ common_name               <chr> "bonsai bamboo coral", "starry cup coral", "…
$ component                 <chr> "coral", "coral", "coral", "coral", "coral",…
$ Kingdom                   <chr> "Animalia", "Animalia", "Animalia", "Animali…
$ Phylum                    <chr> "Cnidaria", "Cnidaria", "Cnidaria", "Cnidari…
$ Subphylum                 <chr> "Anthozoa", "Anthozoa", "Anthozoa", "Anthozo…
$ Class                     <chr> "Octocorallia", "Hexacorallia", "Hexacoralli…
$ Order                     <chr> "Scleralcyonacea", "Scleractinia", "Scleract…
$ Family                    <chr> "Keratoisididae", "Lobophylliidae", "Lobophy…
$ Genus                     <chr> "Acanella", "Acanthastrea", "Acanthastrea", …
$ Species                   <chr> "Acanella arbuscula", "Acanthastrea brevis",…
$ Suborder                  <chr> NA, "Vacatina", "Vacatina", "Vacatina", "Vac…
$ Superfamily               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Subgenus                  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Subfamily                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Infraphylum               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Parvphylum                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Gigaclass                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Superclass                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Subclass                  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Infraclass                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Superorder                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Subkingdom                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Infrakingdom              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Tribe                     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Subspecies                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ `Phylum (Division)`       <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ `Subphylum (Subdivision)` <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Infraorder                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Subterclass               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Section                   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Subsection                <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Parvorder                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Epifamily                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Megaclass                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …

4 Interactive Collapsible Tree

Show code
# Prepare data for collapsible tree
if (taxonomy_source == "worms") {
  # Select available taxonomic levels, handling different column names
  available_cols <- names(hierarchy_df)
  
  # Common taxonomic ranks to look for
  rank_order <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species",
                  "kingdom", "phylum", "class", "order", "family", "genus", "species")
  
  # Find which columns are present
  present_ranks <- rank_order[rank_order %in% available_cols]
  
  if (length(present_ranks) >= 2) {
    tree_data <- hierarchy_df %>%
      select(any_of(c(present_ranks, "scientific_name", "common_name", "component"))) %>%
      mutate(across(everything(), ~replace_na(., "Unknown")))
  } else {
    # Fallback structure
    tree_data <- hierarchy_df %>%
      mutate(
        Level1 = component,
        Level2 = substr(scientific_name, 1, 1),
        Level3 = scientific_name
      ) %>%
      select(Level1, Level2, Level3, scientific_name, common_name)
  }
  
} else {
  tree_data <- hierarchy_df %>%
    select(kingdom, phylum, class, order, family, genus, species, common) %>%
    mutate(across(everything(), ~replace_na(., "Unknown")))
}

# Create collapsible tree
tree_cols <- names(tree_data)[1:min(6, ncol(tree_data))]  # Use first 6 columns max

collapsible_tree <- collapsibleTree(
  tree_data,
  hierarchy = tree_cols,
  width = "100%",
  height = 600,
  zoomable = TRUE,
  collapsed = FALSE,
  nodeSize = "leafCount",
  tooltipHtml = "common",
  fill = "lightblue"
)

collapsible_tree

5 Interactive Network Visualization

Show code
# Create edge list for network visualization
create_edge_list <- function(df, hierarchy_cols) {
  edges <- data.frame()
  
  for (i in 1:(length(hierarchy_cols) - 1)) {
    temp_edges <- df %>%
      select(source = all_of(hierarchy_cols[i]), 
             target = all_of(hierarchy_cols[i + 1])) %>%
      distinct() %>%
      filter(!is.na(source), !is.na(target))
    
    edges <- rbind(edges, temp_edges)
  }
  
  return(edges)
}

# Get hierarchy columns
if (exists("tree_cols")) {
  edges <- create_edge_list(tree_data, tree_cols[1:min(4, length(tree_cols))])
  
  # Create nodes dataframe
  all_nodes <- unique(c(edges$source, edges$target))
  nodes <- data.frame(
    name = all_nodes,
    group = rep(1:min(5, length(all_nodes)/5), length.out = length(all_nodes)),
    size = runif(length(all_nodes), 5, 20)
  )
  
  # Convert edges to use node indices
  edges$source_id <- match(edges$source, nodes$name) - 1
  edges$target_id <- match(edges$target, nodes$name) - 1
  
  # Create force network
  network <- forceNetwork(
    Links = edges,
    Nodes = nodes,
    Source = "source_id",
    Target = "target_id",
    NodeID = "name",
    Group = "group",
    Value = "size",
    opacity = 0.9,
    zoom = TRUE,
    legend = TRUE,
    width = "100%",
    height = 600,
    fontSize = 12,
    charge = -50,
    linkDistance = 50
  )
  
  network
}

6 Hierarchical Sunburst Chart

Show code
# Prepare data for sunburst
if (exists("tree_data") && ncol(tree_data) >= 3) {
  # Create path strings for sunburst
  sunburst_data <- tree_data %>%
    slice_head(n = 100) %>%  # Limit for performance
    mutate(
      path = paste(
        tree_cols[1],
        if(length(tree_cols) > 1) tree_cols[2] else "",
        if(length(tree_cols) > 2) tree_cols[3] else "",
        sep = "-"
      ),
      value = 1
    ) %>%
    group_by(path) %>%
    summarise(value = n(), .groups = "drop")
  
  # Create sunburst using plotly
  fig <- plot_ly(
    labels = sunburst_data$path,
    parents = "",
    values = sunburst_data$value,
    type = 'sunburst',
    branchvalues = "total",
    hovertemplate = '<b>%{label}</b><br>Count: %{value}<extra></extra>'
  ) %>%
    layout(
      title = paste("Taxonomic Hierarchy Sunburst -", 
                   ifelse(taxonomy_source == "worms", "WoRMS", "Birds of the World")),
      height = 600
    )
  
  fig
}

7 Interactive Hierarchical Treemap

Show code
# Prepare data for treemap visualization
if (exists("hierarchy_df")) {
  # Select hierarchical columns in order: Kingdom, Phylum, Class, Order, Family, Species
  available_cols <- names(hierarchy_df)

  # Define taxonomic ranks to include (in hierarchical order)
  desired_ranks <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species",
                     "kingdom", "phylum", "class", "order", "family", "genus", "species",
                     "scientific_name")

  # Find which columns are present
  present_ranks <- desired_ranks[desired_ranks %in% available_cols]

  if (length(present_ranks) >= 1) {
    # Prepare treemap data with component first
    treemap_data <- hierarchy_df %>%
      select(component, any_of(present_ranks)) %>%
      mutate(across(everything(), ~replace_na(as.character(.), "Unknown"))) %>%
      mutate(count = 1)  # Each row represents one species

    # Standardize column names for consistency
    rank_map <- c(
      "Kingdom" = "Kingdom", "kingdom" = "Kingdom",
      "Phylum" = "Phylum", "phylum" = "Phylum",
      "Class" = "Class", "class" = "Class",
      "Order" = "Order", "order" = "Order",
      "Family" = "Family", "family" = "Family",
      "Genus" = "Genus", "genus" = "Genus",
      "Species" = "Species", "species" = "Species", "scientific_name" = "Species"
    )

    # Rename columns to standard names
    for (old_name in names(treemap_data)) {
      if (old_name %in% names(rank_map) && old_name != "count") {
        new_name <- rank_map[[old_name]]
        if (!(new_name %in% names(treemap_data)) || old_name == new_name) {
          names(treemap_data)[names(treemap_data) == old_name] <- new_name
        }
      }
    }

    # Identify taxonomic levels with variety (more than 1 unique value per component)
    rank_order <- c("Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species")
    available_ranks <- rank_order[rank_order %in% names(treemap_data)]

    # Check which ranks show variety within each component
    ranks_with_variety <- c()
    for (rank in available_ranks) {
      variety_check <- treemap_data %>%
        group_by(component) %>%
        summarise(n_unique = n_distinct(.data[[rank]]), .groups = "drop")

      # Include rank if any component has more than 1 unique value
      if (any(variety_check$n_unique > 1)) {
        ranks_with_variety <- c(ranks_with_variety, rank)
      }
    }

    cat("Taxonomic ranks with variety:", paste(ranks_with_variety, collapse = ", "), "\n")

    # Build hierarchy: component first, then ranks with variety
    hierarchy_cols <- c("component", ranks_with_variety) |> 
      setdiff(c("Genus", "Species")) |> 
      c("scientific_name")

    if (length(hierarchy_cols) >= 2) {
      # Create the treemap
      # tm <- treemap(
      #   treemap_data,
      #   index      = hierarchy_cols,
      #   vSize      = "count",
      #   type       = "index",
      #   title      = paste("Hierarchical Taxonomy by Component -",
      #                     ifelse(taxonomy_source == "worms", "WoRMS", "BOTW")),
      #   palette    = "Set3",
      #   fontsize.labels = c(15, 12, 10, 8, 6, 4),
      #   fontcolor.labels = c("white", "white", "black", "black", "black", "black"),
      #   fontface.labels = c(2, 2, 1, 1, 1, 1),
      #   bg.labels   = c("transparent"),
      #   align.labels = list(c("center", "center"), c("left", "top"), c("right", "bottom")),
      #   overlap.labels = 0.5,
      #   inflate.labels = FALSE)
      # 
      # Create interactive d3 treemap
      # d3tree <- d3tree2(
      #   tm,
      #   rootname = "All Species")
      # 
      # d3tree
      
      treemap_data |> 
        select(all_of(hierarchy_cols), n = count) |>
        count_to_treemap()
    } else {
      cat("Not enough taxonomic variety for treemap visualization\n")
    }
  } else {
    cat("No taxonomic ranks available for treemap visualization\n")
  }
}
Taxonomic ranks with variety: Kingdom, Phylum, Class, Order, Family, Genus, Species 

8 Component Distribution

Show code
# Visualize distribution by component
component_summary <- filtered_df %>%
  group_by(component) %>%
  summarise(
    count = n(),
    unique_species = n_distinct(scientific),
    .groups = "drop"
  ) %>%
  arrange(desc(count))

# Create interactive bar chart
fig_comp <- plot_ly(
  data = component_summary,
  x = ~reorder(component, count),
  y = ~count,
  type = 'bar',
  text = ~paste("Component:", component, 
                "<br>Count:", count,
                "<br>Unique species:", unique_species),
  hovertemplate = '%{text}<extra></extra>',
  marker = list(color = 'rgba(50, 171, 96, 0.6)',
                line = list(color = 'rgba(50, 171, 96, 1.0)', width = 1))
) %>%
  layout(
    title = "Distribution of Species by Component",
    xaxis = list(title = "Component"),
    yaxis = list(title = "Number of Records"),
    height = 400
  )

fig_comp

9 Data Summary Table

Show code
# Create summary statistics
summary_stats <- filtered_df %>%
  group_by(component) %>%
  summarise(
    `Total Records` = n(),
    `Unique Species` = n_distinct(scientific),
    `With Common Names` = sum(!is.na(common)),
    `Avg Area (km²)` = round(mean(area_km2, na.rm = TRUE), 2),
    `Avg Suitability` = round(mean(avg_suit, na.rm = TRUE), 3),
    .groups = "drop"
  )

# Display as interactive table
DT::datatable(
  summary_stats,
  options = list(
    pageLength = 10,
    scrollX = TRUE,
    dom = 'Bfrtip',
    buttons = c('copy', 'csv', 'excel')
  ),
  class = 'cell-border stripe',
  caption = paste("Summary Statistics for", 
                 ifelse(taxonomy_source == "worms", "WoRMS", "BOTW"), 
                 "Taxonomy")
)

10 Export Options

Show code
# Save the hierarchical data for further analysis
if (exists("hierarchy_df")) {
  output_file <- here(glue("data/taxonomic_hierarchy_{taxonomy_source}_{Sys.Date()}.csv"))
  
  write_csv(hierarchy_df, output_file)
  cat("Hierarchical data saved to:", output_file, "\n")
}
Hierarchical data saved to: /Users/bbest/Github/MarineSensitivity/workflows/data/taxonomic_hierarchy_worms_2025-10-30.csv 
Show code
# Create a simplified tree structure for export
if (exists("tree_data")) {
  tree_export <- tree_data %>%
    slice_head(n = 100)
  
  tree_file <- here(glue("data/taxonomic_tree_{taxonomy_source}_{Sys.Date()}.csv"))

  write_csv(tree_export, tree_file)
  cat("Tree structure saved to:", tree_file, "\n")
}

11 Notes and Configuration

11.1 Switching Between Taxonomies

To switch between WoRMS and Birds of the World taxonomies, change the taxonomy_source variable in the “Taxonomic Data Selection” chunk:

  • taxonomy_source <- "worms" for marine species from WoRMS
  • taxonomy_source <- "botw" for birds from Birds of the World

11.2 Performance Considerations

  • The code currently limits WoRMS API calls to 50 species for demonstration
  • Remove the slice_head(n = 50) line in the hierarchy fetching section for full analysis
  • Large datasets may take time to process due to API rate limits

11.3 Cache File and Resume Capability

  • Classifications are automatically saved to data/worms_classifications_cache.csv
  • Progress is saved every 10 species to minimize data loss if processing fails
  • On restart, the script automatically loads cached data and skips already-processed species
  • To start fresh, delete the cache file before running the script
  • The cache file is specific to WoRMS taxonomy (BOTW doesn’t use API calls)

11.4 Required Packages

Ensure you have installed:

install.packages(c("tidyverse", "worrms", "collapsibleTree",
                   "networkD3", "plotly", "DT", "htmlwidgets",
                   "treemap", "d3treeR"))

11.5 Customization Options

  1. Tree Visualization: Adjust collapsed = FALSE to TRUE for initially collapsed tree
  2. Network Force: Modify charge and linkDistance parameters for different layouts
  3. Sunburst Depth: Add more hierarchy levels by extending the path construction
  4. Treemap: Customize color palette (e.g., “Set3”, “RdYlBu”, “Spectral”) and font sizes in the treemap() function
  5. Color Schemes: Customize colors in plotly and network visualizations