Interactive map showing Laws revegetation parcels and transect locations. Click on transects for details and use the layer control to toggle different features.
Laws Revegetation Data Analysis
Inyo County, California
Background
The Laws Revegetation Project is based on the Laws Negative Declaration and the Laws Revegetation Plan describing 193 acres that would be reclassified from Type E to Type A. The reclassified land was to be revegetated and not irrigated in the future. This data report focuses on the revegetation targets for the Laws vegetation parcels: LAW090, LAW094, LAW095, LAW118/LAW129 that were part of this reclassification effort. data originate from 2022-2025.
A forthcoming amendment is being developed by the Inyo/LA Technical Group detailing any changes to the 2003 plan needed going forward. This analysis applies species-specific filtering according to the 2003 plan and policy-based capping rules recommended by the working group to assess revegetation targets.
Revegetation Targets:
- Perennial Cover ≥ 10%: Parcel-average native perennial cover from list of allowed species, with specific capping implemented for ATTO, ERNA10 and ATPO.
- Species with ≥3 Hits ≥ 6: At least six perennial species with 3+ hits - from list of allowed species plus ATTO, ERNA10 and ATPO.
- Species Richness ≥ 10: At least 10 distinct perennial species from list of allowed species plus ATTO, ERNA10 and ATPO
- Transect Cover ≥ 2%: Each transect must have ≥2% perennial cover from list of allowed species plus ATTO, ERNA10 and ATPO
- Grass Species Present: At least one grass species per parcel from list of allowed species
Capping Rules: - LAW090/094/095: ATTO and ERNA10 capped at 0.3% maximum contribution per parcel LAW090 Exception: No limit on ATPO species for LAW090 parcel LAW118/129: ATPO capped at 3%, ATTO and ERNA10 capped at 2% each
Overall Target Achievement: Requires ALL targets to be met (cover AND richness AND transect coverage AND grass presence)
Legend: ✓ = Target attained | ✗ = Target not attained
Detailed Methodology
Target 1: Perennial Cover ≥ 10% - Calculation: Sum hits for all allowable species per transect, convert to percentage cover - Formula: (hits / 200) × 100 per transect, then average across parcel - Capping: Apply species-specific limits before final calculation - Threshold: Final cover must be ≥ 10%
Target 2: Species with ≥3 Hits ≥ 6 - Method: Count species with 3+ hits from allowed species list - Process: Identify species meeting threshold, count total - Threshold: Must have at least 6 species with 3+ hits
Target 3: Species Richness ≥ 10 - Method: Count distinct perennial species from allowed species list - Process: Count unique species present in parcel - Threshold: Must have at least 10 distinct species
Target 4: Transect Cover ≥ 2% - Method: Calculate cover for each individual transect - Process: Ensure each transect meets 2% threshold - Assessment: Calculate compliance rate as percentage of transects meeting threshold
Target 5: Grass Species Present - Method: Verify at least one grass species per parcel - Process: Check for grass species from allowed species list - Assessment: Mark as compliant if any grass species found
Location
Parcels and transect locations are shown in the map below. Click on transects for details and use the layer control to toggle different features.
Data Processing
Step 1: Load and Combine Data Sources
LAW090/094/095 Parcels (2022-2025) - Load from LADWP Excel file covering 2022-2025 - Species hits zeroed if not on allowed species list - Cover conversion: cover = hits × (1/200) × 100 - Raw hits converted to percentage cover for analysis
LAW118/129 Parcels (2022-2025) - Load 2025 data from ICWD staff CSV with special parsing - Combine LAW118 and LAW129 into single “LAW118/129” parcel - Apply same cover conversion formula as other parcels - Maintain consistent naming conventions across all years
Data Integration - Apply uniform naming conventions and cover calculations - Ensure consistent data structure across all parcel-year combinations - Validate data completeness and quality
Code
# Load ALL data from LawsRevegetationData Excel file (Species List Data sheet)
# This file contains LAW090, LAW094, LAW095 for all years (2022-2025)
data_law909495 <- read_excel("data/LawsRevegetationData_SummaryTable_2025_ForICWD092525.xlsx",
sheet = "Species List Data") %>%
clean_names() %>%
select(parcel, year, transect, species, cover) %>%
rename(hits = cover) %>% # Rename cover to hits for consistency
mutate(
year = as.numeric(year),
transect = as.character(transect), # Ensure transect is character
species = str_trim(species) # Remove leading/trailing spaces from species names
) %>%
filter(year >= 2022 & year <= 2025)
# %>%
# filter(!str_detect(transect, "999"))
# Load LAW118/129 2025 data from CSV (different format)
# This file has a special format with metadata in first 3 rows
# Read without headers to preserve the raw structure
law118_129_raw <- read_csv("data/raw/reveg/LAW118_129_reveg2025_e.csv",
col_names = FALSE,
show_col_types = FALSE)
# Extract metadata from first 3 rows
# Row 1: Parcel names (LAW118, LAW129)
# Row 2: Transect numbers
# Row 3: Bearing numbers
parcel_names <- as.character(law118_129_raw[1, -1]) # Skip first column
transect_numbers <- as.character(law118_129_raw[2, -1])
bearing_numbers <- as.numeric(law118_129_raw[3, -1])
# Data processing summary for LAW118/129
cat("LAW118/129 data processing:\n",
"- Raw data dimensions:", nrow(law118_129_raw), "x", ncol(law118_129_raw), "\n",
"- Parcel names:", paste(unique(parcel_names), collapse = ", "), "\n",
"- Total transects (LAW118 + LAW129):", length(transect_numbers), "\n",
"- Unique transect numbers:", paste(unique(transect_numbers), collapse = ", "), "\n",
"- Note: Transect numbers overlap between LAW118 and LAW129, will create unique identifiers\n")LAW118/129 data processing:
- Raw data dimensions: 10 x 21
- Parcel names: LAW118, LAW129
- Total transects (LAW118 + LAW129): 20
- Unique transect numbers: 6, 8, 10, 11, 13, 14, 1, 2, 3, 5, 7, 12, 15, 17, 21
- Note: Transect numbers overlap between LAW118 and LAW129, will create unique identifiers
Code
# Process species data (rows 4+)
species_data <- law118_129_raw[4:nrow(law118_129_raw), ]
# Create the long format data for LAW118/129 manually
law118_129_processed <- data.frame()
# Loop through each species (rows 4+)
for (i in 1:nrow(species_data)) {
species_name <- as.character(species_data[i, 1])
# Loop through each transect (columns 2+)
for (j in 2:ncol(species_data)) {
hits_value <- as.numeric(species_data[i, j])
if (!is.na(hits_value) && hits_value > 0) {
# Create a row for this species-transect combination
# Combine LAW118 and LAW129 into single "LAW118/129" parcel
parcel_name <- ifelse(parcel_names[j-1] %in% c("LAW118", "LAW129"), "LAW118/129", parcel_names[j-1])
# Create unique transect identifier by combining parcel and transect number
unique_transect <- paste0(parcel_names[j-1], "_", transect_numbers[j-1])
new_row <- data.frame(
parcel = parcel_name,
year = 2025,
transect = unique_transect,
species = str_trim(species_name),
hits = hits_value
)
law118_129_processed <- bind_rows(law118_129_processed, new_row)
}
}
}
# Combine all data
data_combined <- bind_rows(data_law909495, law118_129_processed)
# Fix parcel naming for consistency - combine LAW118 and LAW129 into "LAW118/129"
data_combined <- data_combined %>%
mutate(
parcel = case_when(
parcel == "LAW129" & year == 2022 ~ "LAW118/129",
parcel == "LAW118" & year == 2023 ~ "LAW118/129", # Add LAW118 2023 to LAW118/129
TRUE ~ parcel
)
)
# Display data processing summary
cat("Data processing summary:\n",
"- LAW090/094/095 data:", nrow(data_law909495), "rows\n",
"- LAW118/129 2025 data:", nrow(law118_129_processed), "rows\n",
"- Combined data:", nrow(data_combined), "rows\n",
"- Unique parcels:", paste(unique(data_combined$parcel), collapse = ", "), "\n",
"- Years covered:", paste(sort(unique(data_combined$year)), collapse = ", "), "\n")Data processing summary:
- LAW090/094/095 data: 826 rows
- LAW118/129 2025 data: 55 rows
- Combined data: 881 rows
- Unique parcels: LAW090, LAW094, LAW095, LAW118/129
- Years covered: 2022, 2023, 2024, 2025
Code
# Visual audit: Show sample of LAW090/094/095 data
cat("\n=== LAW090/094/095 Data Sample ===\n")
=== LAW090/094/095 Data Sample ===
Code
print(head(data_law909495, 10))# A tibble: 10 × 5
parcel year transect species hits
<chr> <dbl> <chr> <chr> <dbl>
1 LAW090 2022 61 ATPO 3
2 LAW090 2022 61 SATR12 0
3 LAW090 2022 61 ATCA2 2
4 LAW090 2022 61 ATTO 11
5 LAW090 2022 61 SAVE4 6
6 LAW090 2022 61 POFR 0
7 LAW090 2022 61 SPAI 0
8 LAW090 2022 62 ERNA10 9
9 LAW090 2022 62 ATPO 10
10 LAW090 2022 62 SATR12 0
Code
# Visual audit: Show sample of LAW118/129 data
cat("\n=== LAW118/129 2025 Data Sample ===\n")
=== LAW118/129 2025 Data Sample ===
Code
print(head(law118_129_processed, 10)) parcel year transect species hits
1 LAW118/129 2025 LAW118_6 ERNA10 8
2 LAW118/129 2025 LAW118_8 ERNA10 16
3 LAW118/129 2025 LAW118_13 ERNA10 1
4 LAW118/129 2025 LAW129_1 ERNA10 38
5 LAW118/129 2025 LAW129_2 ERNA10 82
6 LAW118/129 2025 LAW129_3 ERNA10 45
7 LAW118/129 2025 LAW129_5 ERNA10 72
8 LAW118/129 2025 LAW129_6 ERNA10 6
9 LAW118/129 2025 LAW129_7 ERNA10 4
10 LAW118/129 2025 LAW129_8 ERNA10 54
Code
# Visual audit: Show combined data summary
cat("\n=== Combined Data Summary ===\n")
=== Combined Data Summary ===
Code
data_combined %>%
group_by(parcel, year) %>%
summarise(
n_transects = n_distinct(transect),
n_species = n_distinct(species),
total_hits = sum(hits),
.groups = 'drop'
) %>%
print()# A tibble: 12 × 5
parcel year n_transects n_species total_hits
<chr> <dbl> <int> <int> <dbl>
1 LAW090 2022 20 15 608
2 LAW090 2024 20 10 523
3 LAW090 2025 30 8 749
4 LAW094 2022 20 11 401
5 LAW094 2024 20 7 386
6 LAW094 2025 20 10 339
7 LAW095 2022 20 12 440
8 LAW095 2024 20 8 446
9 LAW095 2025 20 8 297
10 LAW118/129 2022 20 13 648
11 LAW118/129 2023 15 16 682
12 LAW118/129 2025 20 7 985
Code
# Note about LAW118/129 transect counting
cat("\nNote: LAW118/129 transects use unique identifiers (e.g., LAW118_6, LAW129_6) to handle overlapping transect numbers between parcels.\n")
Note: LAW118/129 transects use unique identifiers (e.g., LAW118_6, LAW129_6) to handle overlapping transect numbers between parcels.
Code
# Visual audit: Show hit-to-cover conversion
cat("\n=== Hit-to-Cover Conversion Example ===\n")
=== Hit-to-Cover Conversion Example ===
Code
# Show sample data with hit-to-cover conversion
# cover_percent is the species cover at the transect level (hits/200*100)
sample_data <- data_combined %>%
filter(parcel == "LAW090", year == 2025) %>%
head(10) %>%
mutate(
cover_percent = hits / 200 * 100 # Convert hits to cover percentage at transect level
)
print(sample_data, width = Inf)# A tibble: 10 × 6
parcel year transect species hits cover_percent
<chr> <dbl> <chr> <chr> <dbl> <dbl>
1 LAW090 2025 61 SAVE4 11 5.5
2 LAW090 2025 61 ATPO 7 3.5
3 LAW090 2025 61 ATTO 10 5
4 LAW090 2025 62 ATTO 31 15.5
5 LAW090 2025 62 ATCA2 5 2.5
6 LAW090 2025 62 ERNA10 1 0.5
7 LAW090 2025 63 ATCA2 4 2
8 LAW090 2025 64 SATR12 0 0
9 LAW090 2025 64 ATCA2 13 6.5
10 LAW090 2025 64 ERNA10 1 0.5
Code
# Visual audit: Show transect-to-parcel averaging
cat("\n=== Transect-to-Parcel Averaging Example ===\n")
=== Transect-to-Parcel Averaging Example ===
Code
# Show how transect-level cover is averaged to parcel-level cover
# First, calculate transect-level total cover
transect_totals <- data_combined %>%
filter(parcel == "LAW090", year == 2025) %>%
group_by(transect) %>%
summarise(
transect_total_hits = sum(hits),
transect_cover_percent = sum(hits) / 200 * 100,
.groups = 'drop'
)
# Calculate parcel-level average (mean of all transect cover percentages)
parcel_avg <- mean(transect_totals$transect_cover_percent)
n_transects <- nrow(transect_totals)
# Add parcel-level statistics to each transect row for display
parcel_averaging_example <- transect_totals %>%
mutate(
n_transects = n_transects,
parcel_average_cover = parcel_avg
) %>%
select(transect, transect_total_hits, transect_cover_percent, n_transects, parcel_average_cover)
# Print with all columns visible (width = Inf ensures all columns are shown)
print(parcel_averaging_example, width = Inf, n = Inf)# A tibble: 30 × 5
transect transect_total_hits transect_cover_percent n_transects
<chr> <dbl> <dbl> <int>
1 1 28 14 30
2 10 20 10 30
3 2 12 6 30
4 3 16 8 30
5 4 35 17.5 30
6 5 36 18 30
7 6 33 16.5 30
8 61 28 14 30
9 62 37 18.5 30
10 63 4 2 30
11 64 14 7 30
12 65 23 11.5 30
13 66 16 8 30
14 67 24 12 30
15 68 17 8.5 30
16 69 10 5 30
17 7 19 9.5 30
18 70 17 8.5 30
19 71 11 5.5 30
20 72 21 10.5 30
21 73 44 22 30
22 74 35 17.5 30
23 75 34 17 30
24 76 29 14.5 30
25 77 29 14.5 30
26 78 32 16 30
27 79 14 7 30
28 8 17 8.5 30
29 80 36 18 30
30 9 58 29 30
parcel_average_cover
<dbl>
1 12.5
2 12.5
3 12.5
4 12.5
5 12.5
6 12.5
7 12.5
8 12.5
9 12.5
10 12.5
11 12.5
12 12.5
13 12.5
14 12.5
15 12.5
16 12.5
17 12.5
18 12.5
19 12.5
20 12.5
21 12.5
22 12.5
23 12.5
24 12.5
25 12.5
26 12.5
27 12.5
28 12.5
29 12.5
30 12.5
Step 2: Create Parcel Groups for Analysis
Purpose: Group related parcels together for consistent analysis and rule application
LAW90_94_95 Group - Parcels: LAW090, LAW094, LAW095 - Use same species list and capping rules - Apply strict capping: ERNA10 and ATTO capped at 0.3% each - ATPO species have no limit for LAW090 parcel
LAW118_129 Group - Parcels: LAW118, LAW129 (treated as single management unit) - Use same species list and capping rules - Apply more generous capping: ERNA10/ATTO capped at 2% each, ATPO capped at 3%
Step 3: Apply Species Filtering and Capping Rules
Species Filtering Process 1. Load Allowable Species Lists: Import species lists for each parcel group 2. Filter Data: Keep only species that are on the allowable lists 3. Apply Capping Rules: Limit contribution of specific species (ERNA10, ATTO, ATPO) 4. Calculate Final Cover: Sum capped species contributions for compliance assessment
Capping Rules by Parcel Group - LAW90/94/95: ERNA10 and ATTO capped at 0.3% each, ATPO unlimited for LAW090 - LAW118/129: ERNA10/ATTO capped at 2% each, ATPO capped at 3%
Code
# Use all data without species filtering for now
data_filtered <- data_combined %>%
mutate(
parcel_group = case_when(
parcel %in% c("LAW090", "LAW094", "LAW095") ~ "LAW90_94_95",
parcel %in% c("LAW118", "LAW129") ~ "LAW118_129",
TRUE ~ "Other"
)
)
# Debug: Check data loading
cat("Data filtered rows:", nrow(data_filtered), "\n")Data filtered rows: 881
Code
cat("Data filtered columns:", paste(colnames(data_filtered), collapse = ", "), "\n")Data filtered columns: parcel, year, transect, species, hits, parcel_group
Step 1.6:
Load Allowable Species Lists Purpose: Define which species are allowed for compliance calculations for each parcel group
Two Separate Lists: LAW90/94/95 and LAW118/129 have different allowable species ERNA10, ATTO, ATPO automatically added to both lists.
Code
# Load allowable species from BOTH sheets and combine
# Sheet 1: LAW90/94/95 species
allowable_law90_94_95 <- read_excel("data/TypeE_Transfer_SppList.xlsx", sheet = "TypeE_List_90_94_95") %>%
clean_names() %>%
pull(species_code) %>%
c("ERNA10", "ATTO", "ATPO") %>% # Add core species if not already included
unique()
# Sheet 2: LAW118/129 species
allowable_law118_129 <- read_excel("data/TypeE_Transfer_SppList.xlsx", sheet = "TypeE_List_118_129") %>%
clean_names() %>%
pull(species_code) %>%
c("ERNA10", "ATTO", "ATPO") %>% # Add core species if not already included
unique()
# Debug: Check if SATR12 is in the loaded allowable species
cat("SATR12 in loaded LAW90/94/95:", "SATR12" %in% allowable_law90_94_95, "\n")SATR12 in loaded LAW90/94/95: FALSE
Code
cat("SATR12 in loaded LAW118/129:", "SATR12" %in% allowable_law118_129, "\n")SATR12 in loaded LAW118/129: FALSE
Code
# Create combined allowable species dataframe
allowable_species_combined <- data.frame(
species = c(allowable_law90_94_95, allowable_law118_129),
parcel_group = c(rep("LAW90_94_95", length(allowable_law90_94_95)),
rep("LAW118_129", length(allowable_law118_129)))
) %>%
distinct() # Remove any duplicates
# Debug: Show allowable species lists
cat("Allowable species summary:\n",
"- LAW90/94/95 allowable species:", length(allowable_law90_94_95), "species\n",
"- LAW118/129 allowable species:", length(allowable_law118_129), "species\n",
"- Additional species (ERNA10, ATTO, ATPO) included in both lists\n")Allowable species summary:
- LAW90/94/95 allowable species: 27 species
- LAW118/129 allowable species: 22 species
- Additional species (ERNA10, ATTO, ATPO) included in both lists
Code
# Debug: Check data_filtered
cat("Data filtered rows:", nrow(data_filtered), "\n")Data filtered rows: 881
Code
cat("Data filtered columns:", paste(colnames(data_filtered), collapse = ", "), "\n")Data filtered columns: parcel, year, transect, species, hits, parcel_group
Code
# Calculate compliance metrics with error handling
compliance_metrics <- tryCatch({
data_filtered %>%
group_by(parcel, year) %>%
summarise(
species_richness = n_distinct(species),#need to add trace species also tho
total_transects = n_distinct(transect),
total_hits = if("hits" %in% colnames(data_filtered)) sum(hits, na.rm = TRUE) else 0,
avg_cover_percent = if("hits" %in% colnames(data_filtered)) round(mean((hits / 200) * 100, na.rm = TRUE), 2) else 0,
.groups = 'drop'
) %>%
mutate(
capped_cover_percent = ifelse(avg_cover_percent > 10, 10, avg_cover_percent),
richness_goal = ifelse(species_richness >= 10, "✓", "✗"),
cover_goal = ifelse(avg_cover_percent >= 10, "✓", "✗"),
capped_cover_goal = ifelse(capped_cover_percent >= 10, "✓", "✗"),
overall_compliance = ifelse(richness_goal == "✓" & cover_goal == "✓", "Yes", "No")
)
}, error = function(e) {
cat("Error in compliance_metrics calculation:", e$message, "\n")
# Return empty data frame with correct structure
data.frame(
parcel = character(),
year = numeric(),
species_richness = numeric(),
total_transects = numeric(),
total_hits = numeric(),
avg_cover_percent = numeric(),
capped_cover_percent = numeric(),
richness_goal = character(),
cover_goal = character(),
capped_cover_goal = character(),
overall_compliance = character()
)
})Compliance Results
Comprehensive Parcel-Year Summary
Code
# Debug: Check if compliance_metrics exists and has data
cat("Compliance metrics exists:", exists("compliance_metrics"), "\n")Compliance metrics exists: TRUE
Code
if(exists("compliance_metrics")) {
cat("Compliance metrics rows:", nrow(compliance_metrics), "\n")
cat("Compliance metrics columns:", paste(colnames(compliance_metrics), collapse = ", "), "\n")
}Compliance metrics rows: 12
Compliance metrics columns: parcel, year, species_richness, total_transects, total_hits, avg_cover_percent, capped_cover_percent, richness_goal, cover_goal, capped_cover_goal, overall_compliance
Results
Step 2: Calculate Metrics - Apply species capping rules (ATTO, ERNA10, ATPO limits) - Calculate cover percentages and species richness - Determine compliance status for each parcel-year combination - Remove duplicate entries to ensure data integrity
Code
# Function to calculate cover and capping for a parcel/year
calculate_parcel_cover <- function(parcel, year) {
# Handle LAW118/129 as combined parcel
if (parcel == "LAW118/129") {
# Get data for LAW118/129 (already combined in data processing)
parcel_data <- data_filtered %>%
filter(parcel == "LAW118/129", year == !!year)
# Get original data for transect counting
original_parcel_data <- data_combined %>%
filter(parcel == "LAW118/129", year == !!year)
} else {
# Get data for this specific parcel/year
parcel_data <- data_filtered %>%
filter(parcel == !!parcel, year == !!year)
# Get original data for transect counting
original_parcel_data <- data_combined %>%
filter(parcel == !!parcel, year == !!year)
}
if (nrow(parcel_data) == 0) {
return(NULL)
}
# Determine which species list to use
allowable_species_codes <- if (parcel %in% c("LAW090", "LAW094", "LAW095")) {
allowable_species_combined %>%
filter(parcel_group == "LAW90_94_95") %>%
pull(species)
} else if (parcel == "LAW118/129") {
allowable_species_combined %>%
filter(parcel_group == "LAW118_129") %>%
pull(species)
} else {
character(0)
}
# Filter to only allowable species
parcel_allowable <- parcel_data %>%
filter(species %in% allowable_species_codes)
# Debug: Show species filtering for LAW118/129 2025
if (parcel == "LAW118/129" && year == 2025) {
cat("\n=== Species Filtering for", parcel, year, "===\n")
cat("Allowable species codes:", paste(allowable_species_codes, collapse = ", "), "\n")
cat("Species in data before filtering:", paste(unique(parcel_data$species), collapse = ", "), "\n")
cat("Species after filtering:", paste(unique(parcel_allowable$species), collapse = ", "), "\n")
excluded_species <- setdiff(unique(parcel_data$species), unique(parcel_allowable$species))
if (length(excluded_species) > 0) {
cat("EXCLUDED species (not allowable):", paste(excluded_species, collapse = ", "), "\n")
}
}
# Calculate original cover (sum of all species cover)
n_transects <- n_distinct(original_parcel_data$transect)
original_cover <- if(nrow(parcel_allowable) > 0) {
# Convert hits to cover: hits / 200 * 100
total_cover_percent <- sum(parcel_allowable$hits / 200 * 100)
parcel_average_cover <- total_cover_percent / n_transects
# Debug: Show calculation for LAW118/129 2025
if (parcel == "LAW118/129" && year == 2025) {
cat("\n=== Cover Calculation Debug for", parcel, year, "===\n")
cat("Number of transects:", n_transects, "\n")
cat("Parcel average cover:", round(parcel_average_cover, 2), "%\n")
cat("Species breakdown (before capping):\n")
species_breakdown <- parcel_allowable %>%
group_by(species) %>%
summarise(total_hits = sum(hits), .groups = 'drop') %>%
mutate(
total_cover_percent = round(total_hits / 200 * 100, 2),
species_avg_cover = round(total_cover_percent / n_transects, 2)
) %>%
arrange(desc(species_avg_cover))
print(species_breakdown)
}
parcel_average_cover
} else {
0
}
# Apply capping rules (from backup file)
if(nrow(parcel_allowable) > 0) {
parcel_species_cover <- parcel_allowable %>%
group_by(species) %>%
summarise(
total_hits = sum(hits),
.groups = 'drop'
) %>%
mutate(
species_cover = (total_hits / 200 * 100) / n_transects,
# Apply species-specific capping rules
capped_cover = case_when(
# LAW90/94/95: Strict capping (0.3% max for ERNA10 and ATTO)
parcel %in% c("LAW090", "LAW094", "LAW095") & species == "ERNA10" ~ pmin(species_cover, 0.3),
parcel %in% c("LAW090", "LAW094", "LAW095") & species == "ATTO" ~ pmin(species_cover, 0.3),
# LAW118/129: More generous capping (2% for ERNA10/ATTO, 3% for ATPO)
parcel == "LAW118/129" & species == "ERNA10" ~ pmin(species_cover, 2.0),
parcel == "LAW118/129" & species == "ATTO" ~ pmin(species_cover, 2.0),
parcel == "LAW118/129" & species == "ATPO" ~ pmin(species_cover, 3.0),
# All other species: No capping (full contribution)
TRUE ~ species_cover
)
)
# Calculate final cover after capping
final_cover <- sum(parcel_species_cover$capped_cover)
# Debug: Show capping results for LAW118/129 2025
if (parcel == "LAW118/129" && year == 2025) {
cat("\n=== Species Capping Results for", parcel, year, "===\n")
capping_results <- parcel_species_cover %>%
mutate(
capping_applied = ifelse(species_cover != capped_cover, "YES", "NO"),
reduction = round(species_cover - capped_cover, 2)
) %>%
arrange(desc(capped_cover))
print(capping_results)
cat("Final cover after capping:", round(final_cover, 2), "%\n")
}
} else {
final_cover <- 0
}
result <- data.frame(
Parcel = parcel,
Year = year,
`Cover %` = round(original_cover, 2),
`Transects` = n_transects,
`Cover After Species Caps` = round(final_cover, 2),
`Allowed Sp Count` = n_distinct(parcel_allowable$species),
`Cover Status` = ifelse(final_cover >= 10, "✅ Compliant", "❌ Non-compliant")
)
return(result)
}
# Calculate for all parcels/years based on available data
# Get all unique parcel/year combinations from the data
available_combinations <- data_combined %>%
select(parcel, year) %>%
distinct() %>%
arrange(parcel, year)
# Add LAW118/129 2025 data (combine LAW118 and LAW129)
law118_129_2025 <- calculate_parcel_cover("LAW118/129", 2025)
=== Species Filtering for LAW118/129 2025 ===
Allowable species codes: ACHY, ACSP12, ATCA2, ATCO, ATPA3, ATPO, EPNE, GRSP, KRLA2, LEFR2, MACA17, MESP2, PIDE4, PSARM, PSPO, SAVE4, STEPH, STPI, TEAX, TEGL, ERNA10, ATTO
Species in data before filtering: ERNA10, ATPO, SATR12, ATCA2, COAR4, KRLA2, IVAX
Species after filtering: ERNA10, ATPO, ATCA2, KRLA2
EXCLUDED species (not allowable): SATR12, COAR4, IVAX
=== Cover Calculation Debug for LAW118/129 2025 ===
Number of transects: 20
Parcel average cover: 21.38 %
Species breakdown (before capping):
# A tibble: 4 × 4
species total_hits total_cover_percent species_avg_cover
<chr> <dbl> <dbl> <dbl>
1 ERNA10 635 318. 15.9
2 ATPO 139 69.5 3.48
3 ATCA2 77 38.5 1.93
4 KRLA2 4 2 0.1
=== Species Capping Results for LAW118/129 2025 ===
# A tibble: 4 × 6
species total_hits species_cover capped_cover capping_applied reduction
<chr> <dbl> <dbl> <dbl> <chr> <dbl>
1 ATPO 139 3.48 3 YES 0.48
2 ERNA10 635 15.9 2 YES 13.9
3 ATCA2 77 1.92 1.92 NO 0
4 KRLA2 4 0.1 0.1 NO 0
Final cover after capping: 7.02 %
Code
# Create dashboard data for all available combinations
dashboard_data <- available_combinations %>%
rowwise() %>%
mutate(
result = list(calculate_parcel_cover(parcel, year))
) %>%
ungroup() %>%
filter(!purrr::map_lgl(result, is.null)) %>%
mutate(result = purrr::map(result, as.data.frame)) %>%
pull(result) %>%
bind_rows()
=== Species Filtering for LAW118/129 2025 ===
Allowable species codes: ACHY, ACSP12, ATCA2, ATCO, ATPA3, ATPO, EPNE, GRSP, KRLA2, LEFR2, MACA17, MESP2, PIDE4, PSARM, PSPO, SAVE4, STEPH, STPI, TEAX, TEGL, ERNA10, ATTO
Species in data before filtering: ERNA10, ATPO, SATR12, ATCA2, COAR4, KRLA2, IVAX
Species after filtering: ERNA10, ATPO, ATCA2, KRLA2
EXCLUDED species (not allowable): SATR12, COAR4, IVAX
=== Cover Calculation Debug for LAW118/129 2025 ===
Number of transects: 20
Parcel average cover: 21.38 %
Species breakdown (before capping):
# A tibble: 4 × 4
species total_hits total_cover_percent species_avg_cover
<chr> <dbl> <dbl> <dbl>
1 ERNA10 635 318. 15.9
2 ATPO 139 69.5 3.48
3 ATCA2 77 38.5 1.93
4 KRLA2 4 2 0.1
=== Species Capping Results for LAW118/129 2025 ===
# A tibble: 4 × 6
species total_hits species_cover capped_cover capping_applied reduction
<chr> <dbl> <dbl> <dbl> <chr> <dbl>
1 ATPO 139 3.48 3 YES 0.48
2 ERNA10 635 15.9 2 YES 13.9
3 ATCA2 77 1.92 1.92 NO 0
4 KRLA2 4 0.1 0.1 NO 0
Final cover after capping: 7.02 %
Code
# Add LAW118/129 2025 if it exists
if (!is.null(law118_129_2025)) {
dashboard_data <- bind_rows(dashboard_data, law118_129_2025)
}
# Remove any duplicate rows
dashboard_data <- dashboard_data %>%
distinct(Parcel, Year, .keep_all = TRUE)
# Visual audit: Show analytical results data
cat("\n=== Analytical Results Data ===\n")
=== Analytical Results Data ===
Code
cat("Total rows:", nrow(dashboard_data), "\n")Total rows: 12
Code
cat("Unique parcel-year combinations:", nrow(unique(dashboard_data[, c("Parcel", "Year")])), "\n")Unique parcel-year combinations: 12
Code
# Display wider table
if(nrow(dashboard_data) > 0) {
DT::datatable(
dashboard_data,
caption = 'Analytical Results Table',
options = list(
pageLength = -1, # Show all rows by default
scrollX = FALSE, # Disable horizontal scrolling to make table wider
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4, 5, 6)),
list(width = '80px', targets = 0), # Parcel column
list(width = '60px', targets = 1), # Year column
list(width = '80px', targets = 2), # Cover column
list(width = '80px', targets = 3), # Transects column
list(width = '120px', targets = 4), # Cover After column
list(width = '100px', targets = 5), # Allowed Sp Count column
list(width = '120px', targets = 6) # Cover Status column
),
extensions = 'Buttons',
autoWidth = TRUE
),
rownames = FALSE,
width = '100%'
)
} else {
cat("No data available for display")
}Species Richness Analysis
Step 3: Analyze Species Diversity - Combine transect-detected species with trace species observations - Filter to allowable species only - exclude non-allowable species from richness counts - Calculate total species richness per parcel for 2025 - Include common names for better species identification - Apply consistent LAW118/129 parcel naming
This section analyzes species richness for each parcel in 2025, including both transect-detected species and trace species detected on the parcel. Only allowable species are counted toward the richness targets.
Code
# Load trace species data
trace_species <- read_excel("data/LawsRevegetationData_SummaryTable_2025_ForICWD092525.xlsx",
sheet = "Trace Species") %>%
clean_names() %>%
select(parcel, species = trace_species) %>%
mutate(species = str_trim(species))
# Load species attributes for common names
species_attributes <- read_csv("data/species.csv", show_col_types = FALSE) %>%
select(Code, CommonName) %>%
rename(species = Code, common_name = CommonName)
# Get transect-detected species for 2025 (ALLOWABLE SPECIES ONLY)
transect_species_2025 <- data_combined %>%
filter(year == 2025) %>%
select(parcel, species) %>%
distinct() %>%
# Filter to only allowable species based on parcel group
mutate(
parcel_group = case_when(
parcel %in% c("LAW090", "LAW094", "LAW095") ~ "LAW90_94_95",
parcel %in% c("LAW118", "LAW129") ~ "LAW118_129",
TRUE ~ "Other"
)
) %>%
left_join(allowable_species_combined, by = c("species", "parcel_group")) %>%
filter(!is.na(parcel_group)) %>% # Only keep allowable species
filter(species %in% allowable_species_combined$species) %>% # Double-check filtering
select(parcel, species) %>%
left_join(species_attributes, by = "species") %>%
mutate(
detection_method = "Transect",
# Combine LAW118 and LAW129 into LAW118/129
parcel = ifelse(parcel %in% c("LAW118", "LAW129"), "LAW118/129", parcel)
)
# Get trace species for 2025 (ALLOWABLE SPECIES ONLY)
trace_species_2025 <- trace_species %>%
filter(parcel %in% c("LAW090", "LAW094", "LAW095", "LAW118", "LAW129")) %>%
# Filter to only allowable species based on parcel group
mutate(
parcel_group = case_when(
parcel %in% c("LAW090", "LAW094", "LAW095") ~ "LAW90_94_95",
parcel %in% c("LAW118", "LAW129") ~ "LAW118_129",
TRUE ~ "Other"
)
) %>%
left_join(allowable_species_combined, by = c("species", "parcel_group")) %>%
filter(!is.na(parcel_group)) %>% # Only keep allowable species
filter(species %in% allowable_species_combined$species) %>% # Double-check filtering
select(parcel, species) %>%
left_join(species_attributes, by = "species") %>%
mutate(
detection_method = "Trace",
# Combine LAW118 and LAW129 into LAW118/129
parcel = ifelse(parcel %in% c("LAW118", "LAW129"), "LAW118/129", parcel)
)
# Debug: Show which trace species are being filtered out
cat("\n=== Trace Species Filtering Debug ===\n")
=== Trace Species Filtering Debug ===
Code
trace_before_filter <- trace_species %>%
filter(parcel %in% c("LAW090", "LAW094", "LAW095", "LAW118", "LAW129"))
cat("Trace species before filtering:", nrow(trace_before_filter), "rows\n")Trace species before filtering: 34 rows
Code
cat("Trace species after filtering:", nrow(trace_species_2025), "rows\n")Trace species after filtering: 31 rows
Code
cat("Filtered out:", nrow(trace_before_filter) - nrow(trace_species_2025), "non-allowable species\n")Filtered out: 3 non-allowable species
Code
# Debug: Show which species are in the final trace species list
cat("Final trace species list:\n")Final trace species list:
Code
print(unique(trace_species_2025$species)) [1] "ACHY" "ACSP12" "GRSP" "AMDU2" "PSPO" "LEFR2" "PSARM" "ATCO"
[9] "MESP2" "STEPH" "SAVE4" "KRLA2"
Code
cat("SATR12 in trace species:", "SATR12" %in% trace_species_2025$species, "\n")SATR12 in trace species: FALSE
Code
# Debug: Check if SATR12 is in allowable species lists
cat("SATR12 in LAW90/94/95 allowable:", "SATR12" %in% allowable_law90_94_95, "\n")SATR12 in LAW90/94/95 allowable: FALSE
Code
cat("SATR12 in LAW118/129 allowable:", "SATR12" %in% allowable_law118_129, "\n")SATR12 in LAW118/129 allowable: FALSE
Code
cat("SATR12 in combined allowable:", "SATR12" %in% allowable_species_combined$species, "\n")SATR12 in combined allowable: FALSE
Code
# Combine all species detections
all_species_2025 <- bind_rows(
transect_species_2025,
trace_species_2025
) %>%
# Use common name from species attributes, fallback to species code
mutate(
common_name = case_when(
!is.na(common_name) & common_name != "" ~ common_name,
TRUE ~ species
)
) %>%
select(parcel, species, common_name, detection_method) %>%
distinct()
# Debug: Check if SATR12 is in the final combined species list
cat("SATR12 in final all_species_2025:", "SATR12" %in% all_species_2025$species, "\n")SATR12 in final all_species_2025: FALSE
Code
if("SATR12" %in% all_species_2025$species) {
satr12_rows <- all_species_2025[all_species_2025$species == "SATR12", ]
cat("SATR12 detection method:", paste(unique(satr12_rows$detection_method), collapse = ", "), "\n")
cat("SATR12 parcels:", paste(unique(satr12_rows$parcel), collapse = ", "), "\n")
}
# Visual audit: Show species detection summary
cat("\n=== Species Detection Summary ===\n")
=== Species Detection Summary ===
Code
all_species_2025 %>%
group_by(parcel, detection_method) %>%
summarise(n_species = n(), .groups = 'drop') %>%
print()# A tibble: 8 × 3
parcel detection_method n_species
<chr> <chr> <int>
1 LAW090 Trace 7
2 LAW090 Transect 6
3 LAW094 Trace 9
4 LAW094 Transect 7
5 LAW095 Trace 7
6 LAW095 Transect 5
7 LAW118/129 Trace 1
8 LAW118/129 Transect 4
Code
cat("\n=== Sample Species Detections ===\n")
=== Sample Species Detections ===
Code
print(head(all_species_2025, 15))# A tibble: 15 × 4
parcel species common_name detection_method
<chr> <chr> <chr> <chr>
1 LAW090 SAVE4 Greasewood Transect
2 LAW090 ATPO saltbush, Allscale Transect
3 LAW090 ATTO saltbush, Nevada/Torrey's Transect
4 LAW090 ATCA2 saltbush, Fourwing Transect
5 LAW090 ERNA10 rabbitbrush, Common Transect
6 LAW090 KRLA2 Winterfat Transect
7 LAW094 ATPO saltbush, Allscale Transect
8 LAW094 ATCA2 saltbush, Fourwing Transect
9 LAW094 KRLA2 Winterfat Transect
10 LAW094 LEFR2 alyssum, Desert Transect
11 LAW094 ERNA10 rabbitbrush, Common Transect
12 LAW094 ATTO saltbush, Nevada/Torrey's Transect
13 LAW094 AMDU2 Burrow-bush Transect
14 LAW095 ATCA2 saltbush, Fourwing Transect
15 LAW095 ATTO saltbush, Nevada/Torrey's Transect
Code
# Calculate species richness by parcel
species_richness_2025 <- all_species_2025 %>%
group_by(parcel) %>%
summarise(
total_species = n_distinct(species),
transect_species = n_distinct(species[detection_method == "Transect"]),
trace_species = n_distinct(species[detection_method == "Trace"]),
.groups = 'drop'
) %>%
mutate(
richness_goal_met = total_species >= 10,
status = ifelse(richness_goal_met, "✅ ≥10 species", "❌ <10 species")
)
# Create detailed species list for each parcel
species_lists_2025 <- all_species_2025 %>%
group_by(parcel) %>%
summarise(
species_list = paste(common_name, collapse = ", "),
.groups = 'drop'
)
# Combine richness metrics with species lists
species_richness_table <- species_richness_2025 %>%
left_join(species_lists_2025, by = "parcel") %>%
select(parcel, total_species, transect_species, trace_species, status, species_list)
# Display species richness table
DT::datatable(
species_richness_table,
caption = 'Species Richness Analysis - 2025',
options = list(
pageLength = 10,
scrollX = TRUE,
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4)),
list(width = '100px', targets = 0), # Parcel column
list(width = '80px', targets = 1), # Total species
list(width = '80px', targets = 2), # Transect species
list(width = '80px', targets = 3), # Trace species
list(width = '120px', targets = 4), # Status
list(width = '300px', targets = 5) # Species list
),
extensions = 'Buttons',
autoWidth = TRUE
),
rownames = FALSE,
width = '100%'
) %>%
DT::formatStyle(
'status',
backgroundColor = DT::styleEqual(
c("✅ ≥10 species", "❌ <10 species"),
c("#d4edda", "#f8d7da")
)
)Grass Species Analysis
Step 4: Assess Grass Species Presence - Join species richness data with species attributes to identify grass species - Check if each parcel has at least one grass species (Lifeform = ‘Grass’) - Create summary table showing grass species presence by parcel and year
Code
# Load species attributes for lifeform information
species_attributes_full <- read_csv("data/species.csv", show_col_types = FALSE) %>%
select(Code, CommonName, Lifeform) %>%
rename(species = Code, common_name = CommonName)
# Get all species detections for all years (not just 2025)
all_species_all_years <- data_combined %>%
select(parcel, year, species) %>%
distinct() %>%
# Filter to only allowable species based on parcel group
mutate(
parcel_group = case_when(
parcel %in% c("LAW090", "LAW094", "LAW095") ~ "LAW90_94_95",
parcel %in% c("LAW118", "LAW129") ~ "LAW118_129",
TRUE ~ "Other"
)
) %>%
left_join(allowable_species_combined, by = c("species", "parcel_group")) %>%
filter(!is.na(parcel_group)) %>% # Only keep allowable species
filter(species %in% allowable_species_combined$species) %>% # Double-check filtering
select(parcel, year, species) %>%
# Join with species attributes to get lifeform information
left_join(species_attributes_full, by = "species") %>%
# Apply consistent LAW118/129 naming
mutate(
parcel = ifelse(parcel %in% c("LAW118", "LAW129"), "LAW118/129", parcel)
)
# Debug: Check what species are being detected and their lifeforms
cat("\n=== Grass Species Debug ===\n")
=== Grass Species Debug ===
Code
cat("Sample of species with lifeform data:\n")Sample of species with lifeform data:
Code
print(head(all_species_all_years %>% select(species, Lifeform) %>% distinct(), 10))# A tibble: 10 × 2
species Lifeform
<chr> <chr>
1 ATPO Shrub
2 ATCA2 Shrub
3 ATTO Shrub
4 SAVE4 Shrub
5 ERNA10 Shrub
6 ACHY Grass
7 KRLA2 Shrub
8 PSARM Shrub
9 MACA17 Shrub
10 AMDU2 Shrub
Code
cat("\nUnique lifeforms in data:\n")
Unique lifeforms in data:
Code
print(unique(all_species_all_years$Lifeform))[1] "Shrub" "Grass"
Code
cat("\nSpecies with Lifeform = 'Grass':\n")
Species with Lifeform = 'Grass':
Code
grass_species <- all_species_all_years %>%
filter(Lifeform == "Grass") %>%
select(species, Lifeform) %>%
distinct()
print(grass_species)# A tibble: 1 × 2
species Lifeform
<chr> <chr>
1 ACHY Grass
Code
cat("\nSpecies with grass-related lifeforms (case insensitive):\n")
Species with grass-related lifeforms (case insensitive):
Code
grass_related <- all_species_all_years %>%
filter(str_detect(Lifeform, regex("grass", ignore_case = TRUE))) %>%
select(species, Lifeform) %>%
distinct()
print(grass_related)# A tibble: 1 × 2
species Lifeform
<chr> <chr>
1 ACHY Grass
Code
cat("\nAll species detected in 2025 for LAW090:\n")
All species detected in 2025 for LAW090:
Code
law090_2025 <- all_species_all_years %>%
filter(parcel == "LAW090", year == 2025) %>%
select(species, Lifeform) %>%
distinct()
print(law090_2025)# A tibble: 6 × 2
species Lifeform
<chr> <chr>
1 SAVE4 Shrub
2 ATPO Shrub
3 ATTO Shrub
4 ATCA2 Shrub
5 ERNA10 Shrub
6 KRLA2 Shrub
Code
# Calculate grass species presence by parcel and year
grass_analysis <- all_species_all_years %>%
group_by(parcel, year) %>%
summarise(
total_species = n_distinct(species),
grass_species_count = sum(str_detect(Lifeform, regex("grass", ignore_case = TRUE)), na.rm = TRUE),
grass_species_list = if(grass_species_count > 0) {
paste(unique(species[str_detect(Lifeform, regex("grass", ignore_case = TRUE))]), collapse = ", ")
} else {
"None"
},
.groups = 'drop'
) %>%
mutate(
grass_target_met = grass_species_count > 0,
status = ifelse(grass_target_met, "✅ Grass present", "❌ No grass species")
) %>%
# Shorten column names
rename(
"Total" = total_species,
"Count" = grass_species_count,
"List" = grass_species_list,
"Target" = grass_target_met
)
# Display grass species analysis table
DT::datatable(
grass_analysis,
caption = 'Grass Species Presence Analysis',
options = list(
pageLength = -1, # Show all rows
scrollX = TRUE,
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4, 5, 6)),
list(width = '120px', targets = 0), # Parcel column
list(width = '80px', targets = 1), # Year column
list(width = '120px', targets = 2), # Total species
list(width = '120px', targets = 3), # Grass count
list(width = '140px', targets = 4), # Grass present
list(width = '160px', targets = 5), # Status
list(width = '250px', targets = 6) # Grass species list
),
extensions = 'Buttons',
autoWidth = TRUE
),
rownames = FALSE,
width = '100%'
) %>%
DT::formatStyle(
'status',
backgroundColor = DT::styleEqual(
c("✅ Grass present", "❌ No grass species"),
c("#d4edda", "#f8d7da")
)
)Grass Species Summary
Step 5: Comprehensive Grass Species Analysis - Show all grass species detected (both allowed and non-allowed) - Compare grass species presence between allowed and non-allowed lists - Provide complete grass species inventory by parcel and year
Code
# Get ALL grass species (not just allowable ones)
all_grass_species <- data_combined %>%
select(parcel, year, species) %>%
distinct() %>%
# Join with species attributes to get lifeform information
left_join(species_attributes_full, by = "species") %>%
# Filter for grass species (case insensitive)
filter(str_detect(Lifeform, regex("grass", ignore_case = TRUE))) %>%
# Apply consistent LAW118/129 naming
mutate(
parcel = ifelse(parcel %in% c("LAW118", "LAW129"), "LAW118/129", parcel)
) %>%
# Add allowable status
mutate(
parcel_group = case_when(
parcel %in% c("LAW090", "LAW094", "LAW095") ~ "LAW90_94_95",
parcel == "LAW118/129" ~ "LAW118_129",
TRUE ~ "Other"
)
) %>%
mutate(
is_allowable = species %in% allowable_species_combined$species,
status = ifelse(is_allowable, "✅ Allowed", "❌ Not Allowed")
) %>%
select(parcel, year, species, common_name, Lifeform, is_allowable, status)
# Create summary table of grass species by parcel and year
grass_summary <- all_grass_species %>%
group_by(parcel, year) %>%
summarise(
total_grass_species = n(),
allowed_grass_species = sum(is_allowable),
non_allowed_grass_species = sum(!is_allowable),
grass_species_list = paste(species, collapse = ", "),
allowed_grass_list = if(sum(is_allowable) > 0) {
paste(species[is_allowable], collapse = ", ")
} else {
"None"
},
non_allowed_grass_list = if(sum(!is_allowable) > 0) {
paste(species[!is_allowable], collapse = ", ")
} else {
"None"
},
.groups = 'drop'
) %>%
mutate(
grass_target_met = allowed_grass_species > 0,
status = ifelse(grass_target_met, "✅ Allowed grass present", "❌ No allowed grass")
) %>%
# Remove redundant columns and shorten names
select(-grass_species_list, -non_allowed_grass_list) %>%
rename(
"Total" = total_grass_species,
"Allowed" = allowed_grass_species,
"Non-Allowed" = non_allowed_grass_species,
"Allowed List" = allowed_grass_list,
"Target" = grass_target_met
)
# Display comprehensive grass species summary
DT::datatable(
grass_summary,
caption = 'Comprehensive Grass Species Analysis - All Grass Species (Allowed and Non-Allowed)',
options = list(
pageLength = -1, # Show all rows
scrollX = TRUE,
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4, 5, 6)) # Center align status columns
),
extensions = 'Buttons',
autoWidth = TRUE
),
rownames = FALSE,
width = '100%'
) %>%
DT::formatStyle(
'status',
backgroundColor = DT::styleEqual(
c("✅ Allowed grass present", "❌ No allowed grass"),
c("#d4edda", "#f8d7da")
)
)Code
# Also show detailed grass species table
cat("\n=== Detailed Grass Species by Parcel and Year ===\n")
=== Detailed Grass Species by Parcel and Year ===
Code
DT::datatable(
all_grass_species,
caption = 'Detailed Grass Species List - All Grass Species Detected',
options = list(
pageLength = 20,
scrollX = TRUE,
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4, 5, 6)),
list(width = '100px', targets = 0), # Parcel column
list(width = '60px', targets = 1), # Year column
list(width = '80px', targets = 2), # Species code
list(width = '150px', targets = 3), # Common name
list(width = '100px', targets = 4), # Lifeform
list(width = '100px', targets = 5) # Status
),
extensions = 'Buttons',
autoWidth = TRUE
),
rownames = FALSE,
width = '100%'
) %>%
DT::formatStyle(
'status',
backgroundColor = DT::styleEqual(
c("✅ Allowed", "❌ Not Allowed"),
c("#d4edda", "#f8d7da")
)
)Transect-Level Cover Analysis
Step 6: Assess Transect-Level Cover Requirements - Calculate allowable cover percentage for each individual transect - Check if each transect meets the ≥2% allowable cover requirement - Provide summary statistics of transect compliance - Identify which transects fail to meet the 2% allowable cover threshold
Code
# Function to calculate transect-level cover
calculate_transect_cover <- function(parcel_name, year_value, allowable_species_codes) {
# Filter data for specific parcel and year
parcel_data <- data_combined %>%
filter(parcel == parcel_name, year == year_value)
if (nrow(parcel_data) == 0) {
return(data.frame(
parcel = character(0),
year = integer(0),
transect = character(0),
total_hits = numeric(0),
cover_percentage = numeric(0),
meets_2_percent = logical(0)
))
}
# Calculate cover for each transect
transect_cover <- parcel_data %>%
# Filter to only allowable species
filter(species %in% allowable_species_codes) %>%
group_by(transect) %>%
summarise(
total_hits = sum(hits, na.rm = TRUE),
.groups = 'drop'
) %>%
mutate(
# Convert hits to cover percentage (hits/200 * 100)
cover_percentage = (total_hits / 200) * 100,
meets_2_percent = cover_percentage >= 2.0,
parcel = parcel_name,
year = year_value
) %>%
select(parcel, year, transect, total_hits, cover_percentage, meets_2_percent)
return(transect_cover)
}
# Debug: Check what data is available
cat("\n=== Transect Analysis Debug ===\n")
=== Transect Analysis Debug ===
Code
cat("Available parcels and years in data_combined:\n")Available parcels and years in data_combined:
Code
print(data_combined %>% select(parcel, year) %>% distinct() %>% arrange(parcel, year))# A tibble: 12 × 2
parcel year
<chr> <dbl>
1 LAW090 2022
2 LAW090 2024
3 LAW090 2025
4 LAW094 2022
5 LAW094 2024
6 LAW094 2025
7 LAW095 2022
8 LAW095 2024
9 LAW095 2025
10 LAW118/129 2022
11 LAW118/129 2023
12 LAW118/129 2025
Code
cat("\nAllowable species count:", length(allowable_species_combined$species), "\n")
Allowable species count: 49
Code
cat("Sample allowable species:", head(allowable_species_combined$species, 5), "\n")Sample allowable species: ACHY ACSP12 ELEL5 AMDU2 ATCA2
Code
# Calculate transect cover for all parcels and years
transect_results <- list()
# LAW090
cat("\nProcessing LAW090...\n")
Processing LAW090...
Code
transect_results[["LAW090_2022"]] <- calculate_transect_cover("LAW090", 2022, allowable_species_combined$species)
transect_results[["LAW090_2023"]] <- calculate_transect_cover("LAW090", 2023, allowable_species_combined$species)
transect_results[["LAW090_2024"]] <- calculate_transect_cover("LAW090", 2024, allowable_species_combined$species)
transect_results[["LAW090_2025"]] <- calculate_transect_cover("LAW090", 2025, allowable_species_combined$species)
# LAW094
cat("Processing LAW094...\n")Processing LAW094...
Code
transect_results[["LAW094_2022"]] <- calculate_transect_cover("LAW094", 2022, allowable_species_combined$species)
transect_results[["LAW094_2023"]] <- calculate_transect_cover("LAW094", 2023, allowable_species_combined$species)
transect_results[["LAW094_2024"]] <- calculate_transect_cover("LAW094", 2024, allowable_species_combined$species)
transect_results[["LAW094_2025"]] <- calculate_transect_cover("LAW094", 2025, allowable_species_combined$species)
# LAW095
cat("Processing LAW095...\n")Processing LAW095...
Code
transect_results[["LAW095_2022"]] <- calculate_transect_cover("LAW095", 2022, allowable_species_combined$species)
transect_results[["LAW095_2023"]] <- calculate_transect_cover("LAW095", 2023, allowable_species_combined$species)
transect_results[["LAW095_2024"]] <- calculate_transect_cover("LAW095", 2024, allowable_species_combined$species)
transect_results[["LAW095_2025"]] <- calculate_transect_cover("LAW095", 2025, allowable_species_combined$species)
# LAW118/129 (combined)
cat("Processing LAW118/129...\n")Processing LAW118/129...
Code
transect_results[["LAW118_129_2022"]] <- calculate_transect_cover("LAW118/129", 2022, allowable_species_combined$species)
transect_results[["LAW118_129_2023"]] <- calculate_transect_cover("LAW118/129", 2023, allowable_species_combined$species)
transect_results[["LAW118_129_2025"]] <- calculate_transect_cover("LAW118/129", 2025, allowable_species_combined$species)
# Check results
cat("\nTransect results summary:\n")
Transect results summary:
Code
for(i in names(transect_results)) {
cat(i, ":", nrow(transect_results[[i]]), "rows\n")
}LAW090_2022 : 20 rows
LAW090_2023 : 0 rows
LAW090_2024 : 20 rows
LAW090_2025 : 30 rows
LAW094_2022 : 20 rows
LAW094_2023 : 0 rows
LAW094_2024 : 20 rows
LAW094_2025 : 20 rows
LAW095_2022 : 20 rows
LAW095_2023 : 0 rows
LAW095_2024 : 20 rows
LAW095_2025 : 20 rows
LAW118_129_2022 : 20 rows
LAW118_129_2023 : 15 rows
LAW118_129_2025 : 19 rows
Code
# Combine all results
all_transect_results <- bind_rows(transect_results, .id = "parcel_year") %>%
select(-parcel_year) # Remove the ID column
cat("\nCombined transect results:", nrow(all_transect_results), "rows\n")
Combined transect results: 244 rows
Code
if(nrow(all_transect_results) > 0) {
cat("Sample of combined results:\n")
print(head(all_transect_results, 3))
# Debug: Check LAW118/129 2025 specifically
cat("\n=== LAW118/129 2025 Debug ===\n")
law118_129_2025 <- all_transect_results %>%
filter(parcel == "LAW118/129", year == 2025)
cat("LAW118/129 2025 rows:", nrow(law118_129_2025), "\n")
if(nrow(law118_129_2025) > 0) {
cat("Cover percentages:\n")
print(law118_129_2025$cover_percentage)
cat("Below 2% count:", sum(law118_129_2025$cover_percentage < 2.0), "\n")
cat("Below 2% transects:\n")
below_2 <- law118_129_2025 %>% filter(cover_percentage < 2.0)
print(below_2)
# Check the exact threshold calculation
cat("Threshold check (cover_percentage < 2.0):\n")
print(law118_129_2025$cover_percentage < 2.0)
cat("meets_2_percent values:\n")
print(law118_129_2025$meets_2_percent)
}
} else {
cat("WARNING: No transect results found!\n")
}Sample of combined results:
# A tibble: 3 × 6
parcel year transect total_hits cover_percentage meets_2_percent
<chr> <dbl> <chr> <dbl> <dbl> <lgl>
1 LAW090 2022 61 22 11 TRUE
2 LAW090 2022 62 31 15.5 TRUE
3 LAW090 2022 63 27 13.5 TRUE
=== LAW118/129 2025 Debug ===
LAW118/129 2025 rows: 19
Cover percentages:
[1] 17.5 9.5 20.5 5.5 10.5 24.0 9.0 18.0 31.0 47.5 29.0 35.0 41.0 3.0 28.0
[16] 40.5 10.5 11.0 36.5
Below 2% count: 0
Below 2% transects:
# A tibble: 0 × 6
# ℹ 6 variables: parcel <chr>, year <dbl>, transect <chr>, total_hits <dbl>,
# cover_percentage <dbl>, meets_2_percent <lgl>
Threshold check (cover_percentage < 2.0):
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE
meets_2_percent values:
[1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[16] TRUE TRUE TRUE TRUE
Code
# Debug: Check if we have data for summary
cat("\n=== Summary Table Debug ===\n")
=== Summary Table Debug ===
Code
cat("All transect results structure:\n")All transect results structure:
Code
str(all_transect_results)tibble [244 × 6] (S3: tbl_df/tbl/data.frame)
$ parcel : chr [1:244] "LAW090" "LAW090" "LAW090" "LAW090" ...
$ year : num [1:244] 2022 2022 2022 2022 2022 ...
$ transect : chr [1:244] "61" "62" "63" "64" ...
$ total_hits : num [1:244] 22 31 27 21 43 33 33 15 27 21 ...
$ cover_percentage: num [1:244] 11 15.5 13.5 10.5 21.5 16.5 16.5 7.5 13.5 10.5 ...
$ meets_2_percent : logi [1:244] TRUE TRUE TRUE TRUE TRUE TRUE ...
Code
cat("\nUnique parcels in results:\n")
Unique parcels in results:
Code
print(unique(all_transect_results$parcel))[1] "LAW090" "LAW094" "LAW095" "LAW118/129"
Code
cat("\nUnique years in results:\n")
Unique years in results:
Code
print(unique(all_transect_results$year))[1] 2022 2024 2025 2023
Code
# Create summary statistics
if(nrow(all_transect_results) > 0) {
cat("Creating summary statistics...\n")
transect_summary <- all_transect_results %>%
group_by(parcel, year) %>%
summarise(
total_transects = n(),
transects_meeting_2_percent = sum(meets_2_percent, na.rm = TRUE),
transects_below_2_percent = sum(!meets_2_percent, na.rm = TRUE),
compliance_rate = round((transects_meeting_2_percent / total_transects) * 100, 1),
avg_cover_percentage = round(mean(cover_percentage, na.rm = TRUE), 2),
min_cover_percentage = round(min(cover_percentage, na.rm = TRUE), 2),
max_cover_percentage = round(max(cover_percentage, na.rm = TRUE), 2),
.groups = 'drop'
) %>%
mutate(
status = ifelse(compliance_rate == 100, "✅ All transects ≥2% allowable",
ifelse(compliance_rate >= 80, "⚠️ Most transects ≥2% allowable", "❌ Many transects <2% allowable"))
) %>%
# Rename columns for readability
rename(
"Total Transects" = total_transects,
"Meeting 2%" = transects_meeting_2_percent,
"Below 2%" = transects_below_2_percent,
"Compliance Rate (%)" = compliance_rate,
"Avg Cover (%)" = avg_cover_percentage,
"Min Cover (%)" = min_cover_percentage,
"Max Cover (%)" = max_cover_percentage,
"Status" = status
)
cat("Summary table created with", nrow(transect_summary), "rows\n")
cat("Summary table structure:\n")
str(transect_summary)
cat("Sample summary data:\n")
print(head(transect_summary, 3))
# Debug: Check LAW118/129 2025 summary specifically
cat("\n=== LAW118/129 2025 Summary Debug ===\n")
law118_129_2025_summary <- transect_summary %>%
filter(parcel == "LAW118/129", year == 2025)
if(nrow(law118_129_2025_summary) > 0) {
print(law118_129_2025_summary)
} else {
cat("No LAW118/129 2025 summary found!\n")
}
} else {
cat("No data available, creating empty summary...\n")
# Create empty summary when no data
transect_summary <- data.frame(
parcel = character(0),
year = integer(0),
total_transects = integer(0),
transects_meeting_2_percent = integer(0),
transects_below_2_percent = integer(0),
compliance_rate = numeric(0),
avg_cover_percentage = numeric(0),
min_cover_percentage = numeric(0),
max_cover_percentage = numeric(0),
status = character(0)
)
}Creating summary statistics...
Summary table created with 12 rows
Summary table structure:
tibble [12 × 10] (S3: tbl_df/tbl/data.frame)
$ parcel : chr [1:12] "LAW090" "LAW090" "LAW090" "LAW094" ...
$ year : num [1:12] 2022 2024 2025 2022 2024 ...
$ Total Transects : int [1:12] 20 20 30 20 20 20 20 20 20 20 ...
$ Meeting 2% : int [1:12] 20 20 30 20 20 20 20 19 20 20 ...
$ Below 2% : int [1:12] 0 0 0 0 0 0 0 1 0 0 ...
$ Compliance Rate (%): num [1:12] 100 100 100 100 100 100 100 95 100 100 ...
$ Avg Cover (%) : num [1:12] 15.2 13.07 12.48 10.03 9.65 ...
$ Min Cover (%) : num [1:12] 7 4.5 2 5.5 3.5 2.5 3 1 2 4 ...
$ Max Cover (%) : num [1:12] 24.5 24 29 14.5 20.5 18 23.5 29 17.5 34.5 ...
$ Status : chr [1:12] "✅ All transects ≥2% allowable" "✅ All transects ≥2% allowable" "✅ All transects ≥2% allowable" "✅ All transects ≥2% allowable" ...
Sample summary data:
# A tibble: 3 × 10
parcel year `Total Transects` `Meeting 2%` `Below 2%` `Compliance Rate (%)`
<chr> <dbl> <int> <int> <int> <dbl>
1 LAW090 2022 20 20 0 100
2 LAW090 2024 20 20 0 100
3 LAW090 2025 30 30 0 100
# ℹ 4 more variables: `Avg Cover (%)` <dbl>, `Min Cover (%)` <dbl>,
# `Max Cover (%)` <dbl>, Status <chr>
=== LAW118/129 2025 Summary Debug ===
# A tibble: 1 × 10
parcel year `Total Transects` `Meeting 2%` `Below 2%` `Compliance Rate (%)`
<chr> <dbl> <int> <int> <int> <dbl>
1 LAW118/… 2025 19 19 0 100
# ℹ 4 more variables: `Avg Cover (%)` <dbl>, `Min Cover (%)` <dbl>,
# `Max Cover (%)` <dbl>, Status <chr>
Code
# Display transect summary table
DT::datatable(
transect_summary,
caption = 'Transect-Level Cover Analysis Summary - 2% Allowable Cover Requirement',
options = list(
pageLength = -1, # Show all rows
scrollX = TRUE,
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4, 5, 6, 7, 8, 9)),
list(width = '120px', targets = 0), # Parcel column
list(width = '80px', targets = 1), # Year column
list(width = '120px', targets = 2), # Total transects
list(width = '140px', targets = 3), # Meeting 2%
list(width = '120px', targets = 4), # Below 2%
list(width = '160px', targets = 5), # Compliance rate
list(width = '140px', targets = 6), # Avg cover
list(width = '140px', targets = 7), # Min cover
list(width = '140px', targets = 8), # Max cover
list(width = '200px', targets = 9) # Status
),
extensions = 'Buttons',
autoWidth = TRUE
),
rownames = FALSE,
width = '100%'
) %>%
DT::formatStyle(
'Status',
backgroundColor = DT::styleEqual(
c("✅ All transects ≥2% allowable", "⚠️ Most transects ≥2% allowable", "❌ Many transects <2% allowable"),
c("#d4edda", "#fff3cd", "#f8d7da")
)
)Code
# Display detailed transect results
if(nrow(all_transect_results) > 0) {
DT::datatable(
all_transect_results,
caption = 'Detailed Transect-Level Cover Analysis - Individual Transect Results (Allowable Species Only)',
options = list(
pageLength = 20,
scrollX = TRUE,
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4, 5)),
list(width = '100px', targets = 0), # Parcel column
list(width = '60px', targets = 1), # Year column
list(width = '80px', targets = 2), # Transect column
list(width = '80px', targets = 3), # Total hits
list(width = '100px', targets = 4), # Cover percentage
list(width = '100px', targets = 5) # Meets 2%
),
extensions = 'Buttons',
autoWidth = TRUE
),
rownames = FALSE,
width = '100%'
) %>%
DT::formatStyle(
'meets_2_percent',
backgroundColor = DT::styleEqual(
c(TRUE, FALSE),
c("#d4edda", "#f8d7da")
)
) %>%
DT::formatRound(
columns = 'cover_percentage',
digits = 2
)
} else {
cat("\n=== No Transect Data Available ===\n")
cat("No transect-level data found. This could be due to:\n")
cat("1. No data available for the specified parcels/years\n")
cat("2. No allowable species found in the data\n")
cat("3. Data filtering issues\n")
cat("Please check the debug output above for more details.\n")
}Species with 3+ Hits Analysis
Step 7: Assess Species with 3+ Hits Requirement - Count species with 3+ hits for each parcel and year - Check if each parcel meets the ≥6 species with 3+ hits requirement - Filter to only allowable species (including ATTO, ERNA10, ATPO) - Provide summary statistics of species diversity compliance
Code
# Function to calculate species with 3+ hits for each parcel and year
calculate_species_3plus_hits <- function(parcel_name, year_value, allowable_species_codes) {
# Filter data for specific parcel and year
parcel_data <- data_combined %>%
filter(parcel == parcel_name, year == year_value)
if (nrow(parcel_data) == 0) {
return(data.frame(
parcel = parcel_name,
year = year_value,
species_3plus_count = 0,
meets_6_species = FALSE,
species_3plus_list = "None"
))
}
# Calculate species with 3+ hits (allowable species only)
species_3plus <- parcel_data %>%
# Filter to only allowable species
filter(species %in% allowable_species_codes) %>%
group_by(species) %>%
summarise(
total_hits = sum(hits, na.rm = TRUE),
.groups = 'drop'
) %>%
filter(total_hits >= 3) %>%
arrange(desc(total_hits))
# Create summary
species_3plus_count <- nrow(species_3plus)
meets_6_species <- species_3plus_count >= 6
species_3plus_list <- if(species_3plus_count > 0) {
paste(species_3plus$species, collapse = ", ")
} else {
"None"
}
return(data.frame(
parcel = parcel_name,
year = year_value,
species_3plus_count = species_3plus_count,
meets_6_species = meets_6_species,
species_3plus_list = species_3plus_list
))
}
# Calculate species with 3+ hits for all parcels and years
species_3plus_results <- list()
# LAW090
cat("Processing LAW090 species with 3+ hits...\n")Processing LAW090 species with 3+ hits...
Code
species_3plus_results[["LAW090_2022"]] <- calculate_species_3plus_hits("LAW090", 2022, allowable_species_combined$species)
species_3plus_results[["LAW090_2023"]] <- calculate_species_3plus_hits("LAW090", 2023, allowable_species_combined$species)
species_3plus_results[["LAW090_2024"]] <- calculate_species_3plus_hits("LAW090", 2024, allowable_species_combined$species)
species_3plus_results[["LAW090_2025"]] <- calculate_species_3plus_hits("LAW090", 2025, allowable_species_combined$species)
# LAW094
cat("Processing LAW094 species with 3+ hits...\n")Processing LAW094 species with 3+ hits...
Code
species_3plus_results[["LAW094_2022"]] <- calculate_species_3plus_hits("LAW094", 2022, allowable_species_combined$species)
species_3plus_results[["LAW094_2023"]] <- calculate_species_3plus_hits("LAW094", 2023, allowable_species_combined$species)
species_3plus_results[["LAW094_2024"]] <- calculate_species_3plus_hits("LAW094", 2024, allowable_species_combined$species)
species_3plus_results[["LAW094_2025"]] <- calculate_species_3plus_hits("LAW094", 2025, allowable_species_combined$species)
# LAW095
cat("Processing LAW095 species with 3+ hits...\n")Processing LAW095 species with 3+ hits...
Code
species_3plus_results[["LAW095_2022"]] <- calculate_species_3plus_hits("LAW095", 2022, allowable_species_combined$species)
species_3plus_results[["LAW095_2023"]] <- calculate_species_3plus_hits("LAW095", 2023, allowable_species_combined$species)
species_3plus_results[["LAW095_2024"]] <- calculate_species_3plus_hits("LAW095", 2024, allowable_species_combined$species)
species_3plus_results[["LAW095_2025"]] <- calculate_species_3plus_hits("LAW095", 2025, allowable_species_combined$species)
# LAW118/129 (combined)
cat("Processing LAW118/129 species with 3+ hits...\n")Processing LAW118/129 species with 3+ hits...
Code
species_3plus_results[["LAW118_129_2022"]] <- calculate_species_3plus_hits("LAW118/129", 2022, allowable_species_combined$species)
species_3plus_results[["LAW118_129_2023"]] <- calculate_species_3plus_hits("LAW118/129", 2023, allowable_species_combined$species)
species_3plus_results[["LAW118_129_2025"]] <- calculate_species_3plus_hits("LAW118/129", 2025, allowable_species_combined$species)
# Combine all results
all_species_3plus_results <- bind_rows(species_3plus_results, .id = "parcel_year") %>%
select(-parcel_year) # Remove the ID column
cat("\nSpecies with 3+ hits results:", nrow(all_species_3plus_results), "rows\n")
Species with 3+ hits results: 15 rows
Code
if(nrow(all_species_3plus_results) > 0) {
cat("Sample results:\n")
print(head(all_species_3plus_results, 3))
}Sample results:
parcel year species_3plus_count meets_6_species
1 LAW090 2022 6 TRUE
2 LAW090 2023 0 FALSE
3 LAW090 2024 7 TRUE
species_3plus_list
1 ATPO, ATCA2, ATTO, ERNA10, KRLA2, SAVE4
2 None
3 ATPO, ATCA2, ATTO, ERNA10, AMDU2, KRLA2, SAVE4
Code
# Create summary statistics (simplified) - only show monitored years
# Define which years were actually monitored for each parcel
monitored_years <- list(
"LAW090" = c(2022, 2024, 2025),
"LAW094" = c(2022, 2024, 2025),
"LAW095" = c(2022, 2024, 2025),
"LAW118/129" = c(2022, 2023, 2025)
)
species_3plus_summary <- all_species_3plus_results %>%
# Filter to only show parcel-years that were actually monitored
filter(
(parcel == "LAW090" & year %in% monitored_years[["LAW090"]]) |
(parcel == "LAW094" & year %in% monitored_years[["LAW094"]]) |
(parcel == "LAW095" & year %in% monitored_years[["LAW095"]]) |
(parcel == "LAW118/129" & year %in% monitored_years[["LAW118/129"]])
) %>%
select(parcel, year, species_3plus_count, meets_6_species, species_3plus_list) %>%
mutate(
status = ifelse(meets_6_species, "✅", "❌")
) %>%
# Rename columns for readability
rename(
"Count" = species_3plus_count,
"Target" = meets_6_species,
"Species" = species_3plus_list,
"Status" = status
)
# Display species with 3+ hits summary table
DT::datatable(
species_3plus_summary,
caption = 'Species with 3+ Hits Analysis Summary - 6 Species Requirement',
options = list(
pageLength = -1, # Show all rows
scrollX = TRUE,
dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4, 5)),
list(width = '80px', targets = 0), # Parcel column
list(width = '50px', targets = 1), # Year column
list(width = '60px', targets = 2), # Count
list(width = '60px', targets = 3), # Target
list(width = '150px', targets = 4), # Species list
list(width = '60px', targets = 5) # Status
),
extensions = 'Buttons',
autoWidth = TRUE
),
rownames = FALSE,
width = '100%'
) %>%
DT::formatStyle(
'Status',
backgroundColor = DT::styleEqual(
c("✅", "❌"),
c("#d4edda", "#f8d7da")
)
) %>%
DT::formatStyle(
'Target',
backgroundColor = DT::styleEqual(
c(TRUE, FALSE),
c("#d4edda", "#f8d7da")
)
)Code
# Note: Detailed table removed - all information now in summary table aboveComprehensive Target Status Summary
Step 8: Comprehensive Target Assessment - Combine all revegetation target statuses into a single summary table - Show overall target achievement across all metrics for each parcel-year - Provide complete compliance overview for monitoring and reporting
Column Descriptions
This table provides a comprehensive overview of revegetation target achievement across all Laws parcels and monitoring years. Each column represents a specific target metric:
- Cover: Overall allowable species cover target (≥10% required)
- Grass: Presence of at least one grass species (required for all parcels)
- Transect: Every transect >2% cover
- Species 3+: ≥6 species with 3+ hits
- Overall: Combined status across all target metrics
Status Indicators: - ✅ Green: Target met/compliant - ❌ Red: Target not met/non-compliant
Code
# Debug: Check what columns exist in dashboard_data
cat("Columns in dashboard_data:\n")Columns in dashboard_data:
Code
print(names(dashboard_data))[1] "Parcel" "Year"
[3] "Cover.." "Transects"
[5] "Cover.After.Species.Caps" "Allowed.Sp.Count"
[7] "Cover.Status"
Code
cat("Sample of dashboard_data:\n")Sample of dashboard_data:
Code
print(head(dashboard_data, 2)) Parcel Year Cover.. Transects Cover.After.Species.Caps Allowed.Sp.Count
1 LAW090 2022 15.20 20 11.43 8
2 LAW090 2024 13.07 20 10.00 7
Cover.Status
1 ✅ Compliant
2 ✅ Compliant
Code
# Check if dashboard_data exists and has the right structure
if (!exists("dashboard_data")) {
cat("ERROR: dashboard_data does not exist!\n")
} else {
cat("dashboard_data exists with", nrow(dashboard_data), "rows and", ncol(dashboard_data), "columns\n")
cat("Column names:", paste(names(dashboard_data), collapse = ", "), "\n")
}dashboard_data exists with 12 rows and 7 columns
Column names: Parcel, Year, Cover.., Transects, Cover.After.Species.Caps, Allowed.Sp.Count, Cover.Status
Code
# Get the analytical results (cover targets)
if (exists("dashboard_data") && ncol(dashboard_data) >= 7) {
cover_results <- dashboard_data %>%
select(1, 2, 7) %>% # Parcel, Year, Cover Status (7th column)
rename(parcel = 1, year = 2, cover_status = 3)
# Debug: Check cover_results
cat("Cover results dimensions:", dim(cover_results), "\n")
print(head(cover_results, 2))
} else {
cat("ERROR: dashboard_data doesn't have enough columns or doesn't exist\n")
cover_results <- data.frame()
}Cover results dimensions: 12 3
parcel year cover_status
1 LAW090 2022 ✅ Compliant
2 LAW090 2024 ✅ Compliant
Code
# Get grass species results
if (exists("grass_analysis") && "Target" %in% names(grass_analysis)) {
grass_results <- grass_analysis %>%
select(parcel, year, Target) %>%
mutate(grass_status = ifelse(Target, "✅", "❌")) %>%
select(parcel, year, grass_status) # Remove Target column
# Debug: Check grass_results
cat("Grass results dimensions:", dim(grass_results), "\n")
print(head(grass_results, 2))
} else {
cat("ERROR: grass_analysis doesn't exist or doesn't have Target column\n")
grass_results <- data.frame()
}Grass results dimensions: 12 3
# A tibble: 2 × 3
parcel year grass_status
<chr> <dbl> <chr>
1 LAW090 2022 ✅
2 LAW090 2024 ❌
Code
# Get transect cover results (2% allowable cover)
if (exists("transect_summary") && "Compliance Rate (%)" %in% names(transect_summary)) {
transect_results <- transect_summary %>%
select(parcel, year, `Compliance Rate (%)`) %>%
mutate(
transect_status = case_when(
`Compliance Rate (%)` == 100 ~ "✅",
TRUE ~ "❌"
)
) %>%
select(parcel, year, transect_status)
} else {
cat("ERROR: transect_summary doesn't exist or doesn't have Compliance Rate (%) column\n")
transect_results <- data.frame()
}
# Get species with 3+ hits results
if (exists("all_species_3plus_results") && "meets_6_species" %in% names(all_species_3plus_results)) {
species_3plus_results_summary <- all_species_3plus_results %>%
select(parcel, year, meets_6_species) %>%
mutate(species_3plus_status = ifelse(meets_6_species, "✅", "❌"))
} else {
cat("ERROR: all_species_3plus_results doesn't exist or doesn't have meets_6_species column\n")
species_3plus_results_summary <- data.frame()
}
# Combine all results
if (nrow(cover_results) > 0) {
comprehensive_summary <- cover_results %>%
left_join(grass_results, by = c("parcel", "year")) %>%
left_join(transect_results, by = c("parcel", "year")) %>%
left_join(species_3plus_results_summary, by = c("parcel", "year")) %>%
# Create overall status (two-tiered: All Targets Met or Targets Not Met)
mutate(
overall_status = case_when(
cover_status == "✅ Compliant" &
grass_status == "✅" &
transect_status == "✅" &
species_3plus_status == "✅" ~ "✅ All Targets Met",
TRUE ~ "❌ Targets Not Met"
)
) %>%
# Simplify cover status to emoji only
mutate(
cover_status = case_when(
cover_status == "✅ Compliant" ~ "✅",
cover_status == "❌ Non-compliant" ~ "❌",
TRUE ~ cover_status
)
) %>%
# Remove redundant boolean columns and rename for display
select(-meets_6_species) %>%
rename(
"Cover" = cover_status,
"Grass" = grass_status,
"Transect" = transect_status,
"Species 3+" = species_3plus_status,
"Overall" = overall_status
)
# Debug: Check the comprehensive_summary data
cat("Comprehensive summary dimensions:", dim(comprehensive_summary), "\n")
cat("Comprehensive summary columns:", names(comprehensive_summary), "\n")
print(head(comprehensive_summary, 3))
# Display comprehensive target summary - try different approaches
cat("Creating interactive table...\n")
# Check if DT is available
if (requireNamespace("DT", quietly = TRUE)) {
cat("DT library is available\n")
# Try DT table with proper column widths
tryCatch({
simple_table <- DT::datatable(
comprehensive_summary,
caption = 'Comprehensive Revegetation Target Status Summary - All Parcels and Years (Cover: ≥10% allowable species, Grass: presence required, Every Transect: >2% cover, ≥6 species with 3+ hits, Overall: combined status)',
options = list(
pageLength = -1,
autoWidth = TRUE,
columnDefs = list(
list(className = 'dt-center', targets = c(1, 2, 3, 4, 5, 6)) # Center align status columns
)
),
rownames = FALSE
) %>%
DT::formatStyle(
'Cover',
backgroundColor = DT::styleEqual(
c("✅", "❌"),
c("#d4edda", "#f8d7da")
)
) %>%
DT::formatStyle(
'Grass',
backgroundColor = DT::styleEqual(
c("✅", "❌"),
c("#d4edda", "#f8d7da")
)
) %>%
DT::formatStyle(
'Transect',
backgroundColor = DT::styleEqual(
c("✅", "⚠️", "❌"),
c("#d4edda", "#fff3cd", "#f8d7da")
)
) %>%
DT::formatStyle(
'Species 3+',
backgroundColor = DT::styleEqual(
c("✅", "❌"),
c("#d4edda", "#f8d7da")
)
) %>%
DT::formatStyle(
'Overall',
backgroundColor = DT::styleEqual(
c("✅ All Targets Met", "❌ Targets Not Met"),
c("#d4edda", "#f8d7da")
)
)
cat("DT table created successfully\n")
simple_table
}, error = function(e) {
cat("DT table creation failed:", e$message, "\n")
cat("Falling back to basic table display\n")
knitr::kable(comprehensive_summary, caption = "Comprehensive Revegetation Target Status Summary")
})
} else {
cat("DT library not available, using basic table\n")
knitr::kable(comprehensive_summary, caption = "Comprehensive Revegetation Target Status Summary")
}
} else {
cat("ERROR: Cannot create comprehensive summary - no data available\n")
cat("Cover results rows:", nrow(cover_results), "\n")
cat("Grass results rows:", nrow(grass_results), "\n")
cat("Transect results rows:", nrow(transect_results), "\n")
cat("Species 3+ results rows:", nrow(species_3plus_results_summary), "\n")
}Comprehensive summary dimensions: 12 7
Comprehensive summary columns: parcel year Cover Grass Transect Species 3+ Overall
parcel year Cover Grass Transect Species 3+ Overall
1 LAW090 2022 ✅ ✅ ✅ ✅ ✅ All Targets Met
2 LAW090 2024 ✅ ❌ ✅ ✅ ❌ Targets Not Met
3 LAW090 2025 ✅ ❌ ✅ ✅ ❌ Targets Not Met
Creating interactive table...
DT library is available
DT table created successfully
Data Sources
Overview
This analysis integrates multiple data sources to provide comprehensive revegetation target assessment across all Laws parcels. The following datasets are available for download:
Data Processing Summary
Raw Data Sources - LAW090/094/095: LADWP Excel files (2022-2025) - LAW118/129: ICWD CSV files (2022-2025) - Species Lists: Allowable species by parcel group - Reference Data: Policy thresholds and capping rules
Processing Steps 1. Data Loading: Import and clean raw survey data 2. Species Filtering: Apply allowable species lists 3. Cover Calculation: Convert hits to percentage cover 4. Capping Rules: Apply species-specific limits 5. Compliance Assessment: Evaluate against 5 target criteria 6. Export Results: Generate summary tables and spatial data
Available Datasets
📥 Input Data
Primary Monitoring Data
LAW090/094/095 Parcels (2022-2025) - Source: Los Angeles Department of Water and Power (LADWP) - Files: - 2022 Data - LAW090/094/095 monitoring data - 2024 Data - LAW090/094/095 monitoring data - 2025 Data - LAW090/094/095 Excel data - Content: Point-intercept survey data with species hits per transect - Cover Conversion: Raw hits converted to percentage cover (hits/200 × 100) - Years: 2022, 2024, 2025 (no monitoring in 2023)
LAW118/129 Parcels (2022-2025) - Source: Inyo County Water Department (ICWD) - Files: - 2022 Data - LAW118/129 monitoring data - 2023 Data - LAW118/129 monitoring data - 2025 Data - LAW118/129 monitoring data - Content: Point-intercept survey data for combined LAW118/129 parcel - Cover Conversion: Raw hits converted to percentage cover (hits/200 × 100) - Years: 2022, 2023, 2025 (no monitoring in 2024)
🔧 Auxiliary Data
Species Reference Data
- File: TypeE_Transfer_SppList.xlsx
- Content: Species lists defining allowable species for revegetation targets
- Groups:
- LAW90_94_95: Allowable species for LAW090, LAW094, LAW095
- LAW118_129: Allowable species for LAW118/129 combined parcel
- Core Species: ERNA10, ATTO, ATPO automatically included in all lists
Species Attributes
- File: species.csv - Species codes, names, and classifications
- Content: Complete species database with lifeform, lifecycle, and common names
- Usage: Species identification and classification for analysis
📊 Processed Data
The analysis generates several key datasets that are available for download:
Merged Revegetation Data
- Analytical Results Table - Complete merged dataset with cover conversion and species attributes
- Content: Raw survey data with hits converted to cover percentages, species attributes joined, and capping rules applied
- Usage: Primary dataset for all downstream analysis and compliance calculations
Spatial Data
- Parcel Boundaries - GeoJSON boundaries for all Laws parcels
- Transect Locations - GeoJSON point locations of all monitoring transects
- Transect Data (CSV) - Transect coordinates and metadata in CSV format
📋 Summary Tables
Parcel-Level Summaries
- Parcel Summary - Parcel-level compliance statistics by year
- Species Summary - Species diversity and compliance across all years
- Transect Summary - Transect-level cover analysis and compliance
- Content: Aggregated statistics by parcel and year
- Usage: Parcel-level performance assessment
Compliance Assessment
- Comprehensive Compliance Summary - Main compliance results with all target metrics
- Content: Parcel-year combinations with compliance status for all 5 targets
- Usage: Primary compliance assessment results
Reference Parcel Analysis
- Reference Parcel Summary - Reference parcel statistics and ATTO/ERNA analysis
- ATTO/ERNA Analysis - Detailed analysis of policy-capped species
- Content: Reference parcel data and species analysis
- Usage: Reference data for comparison with revegetation parcels
📦 Data Download
- Download Essential Data - Complete package with all essential datasets (12 files)
Export Results
Comprehensive compliance summary exported to CSV
Transect summary exported to CSV
Species summary for all years exported to CSV
Parcel summary exported to CSV
[1] TRUE
[1] TRUE
✅ Clean downloads package created
📁 Clean directory: output/clean_downloads
📊 Files included: 10
📦 ZIP file: output/clean_revegetation_data.zip
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
[1] TRUE
Results exported to output/ and docs/ directories
Primary and auxiliary data files copied to docs/ directory
Citation
@report{2025,
author = {},
publisher = {Inyo County Water Department},
title = {Laws {Revegetation} {Data} {Analysis}},
date = {2025-09-03},
url = {https://github.com/inyo-gov/revegetation-projects},
langid = {en}
}