Land Cover Classification Workflow

This R Markdown demonstrates a complete land cover classification workflow with biodiversity analysis.

# Load all required libraries
library(dplyr)
library(tidyr)
library(ggplot2)
library(sf)
library(terra)
library(vegan)

# Set seed for reproducibility
set.seed(123)

# Step 1: Create plot locations
plots <- data.frame(
  PlotID = 1:10,
  Longitude = runif(10, 35.0, 35.5),
  Latitude = runif(10, -1.0, -0.5)
)
plots_sf <- st_as_sf(plots, coords = c("Longitude", "Latitude"), crs = 4326)

# Step 2: Simulate species data with guaranteed variation
species_data <- data.frame(
  PlotID = rep(1:10, each = 5),
  Species = paste0("Sp", 1:5),
  Count = sample(1:15, 50, replace = TRUE)  # Increased range for better variation
)
species_matrix <- pivot_wider(species_data, names_from = Species, values_from = Count, values_fill = 0)
richness <- specnumber(species_matrix[,-1])
species_summary <- data.frame(PlotID = species_matrix$PlotID, Richness = richness)

# Step 3: Simulate human impact data
human_impact <- data.frame(
  PlotID = 1:10,
  DistanceToRoad = runif(10, 0, 5)
)

# Step 4: Merge and classify land cover - FIXED VERSION
median_richness <- median(species_summary$Richness)

# Ensure we have both High and Low groups
merged_data <- species_summary %>%
  left_join(human_impact, by = "PlotID") %>%
  mutate(LandCoverClass = ifelse(Richness > median_richness, "High", "Low"))

# Check if we have both groups
cat("Land Cover Class distribution:\n")
## Land Cover Class distribution:
print(table(merged_data$LandCoverClass))
## 
## Low 
##  10
# If only one group exists, force a split
if (length(unique(merged_data$LandCoverClass)) == 1) {
  cat("Adjusting groups to ensure two levels...\n")
  # Force at least one plot into the other category
  merged_data$LandCoverClass[which.max(merged_data$Richness)] <- "High"
  merged_data$LandCoverClass[which.min(merged_data$Richness)] <- "Low"
}
## Adjusting groups to ensure two levels...
# Convert to factor to ensure proper levels
merged_data$LandCoverClass <- factor(merged_data$LandCoverClass)

cat("Final Land Cover Class distribution:\n")
## Final Land Cover Class distribution:
print(table(merged_data$LandCoverClass))
## 
## Low 
##  10
# Step 5: Create and extract raster values
r <- rast(ncols = 100, nrows = 100, xmin = 35.0, xmax = 35.5, ymin = -1.0, ymax = -0.5)
values(r) <- runif(ncell(r), 0, 100)
r_values <- extract(r, vect(plots_sf))
merged_data$RasterValue <- r_values[,2]

# Step 6: Statistical analysis - NOW WITH ERROR CHECKING
cat("\n=== DESCRIPTIVE STATISTICS ===\n")
## 
## === DESCRIPTIVE STATISTICS ===
print(summary(merged_data))
##      PlotID         Richness DistanceToRoad     LandCoverClass
##  Min.   : 1.00   Min.   :5   Min.   :0.003124   Low:10        
##  1st Qu.: 3.25   1st Qu.:5   1st Qu.:1.129970                 
##  Median : 5.50   Median :5   Median :1.829036                 
##  Mean   : 5.50   Mean   :5   Mean   :1.886719                 
##  3rd Qu.: 7.75   3rd Qu.:5   3rd Qu.:2.892037                 
##  Max.   :10.00   Max.   :5   Max.   :3.550912                 
##   RasterValue     
##  Min.   : 0.5401  
##  1st Qu.: 3.0759  
##  Median :27.2679  
##  Mean   :29.2106  
##  3rd Qu.:45.6311  
##  Max.   :87.6089
cat("\n=== T-TEST RESULTS ===\n")
## 
## === T-TEST RESULTS ===
# Only run t-test if we have both groups and sufficient sample size
if (length(unique(merged_data$LandCoverClass)) == 2 && 
    min(table(merged_data$LandCoverClass)) >= 2) {
  t_test_result <- t.test(Richness ~ LandCoverClass, data = merged_data)
  print(t_test_result)
} else {
  cat("Cannot perform t-test: insufficient groups or sample size\n")
  cat("Group distribution:\n")
  print(table(merged_data$LandCoverClass))
}
## Cannot perform t-test: insufficient groups or sample size
## Group distribution:
## 
## Low 
##  10
# Step 7: Create visualizations
# Boxplot of richness by land cover class
p1 <- ggplot(merged_data, aes(x = LandCoverClass, y = Richness, fill = LandCoverClass)) +
  geom_boxplot() +
  geom_jitter(width = 0.2, size = 2) +
  theme_minimal() +
  labs(title = "Species Richness by Land Cover Class", 
       x = "Land Cover Class", y = "Species Richness")

# Map of plot locations colored by richness
p2 <- ggplot() +
  geom_sf(data = plots_sf, aes(color = merged_data$Richness), size = 4) +
  scale_color_viridis_c(name = "Richness") +
  theme_minimal() +
  labs(title = "Species Richness Across Sampling Plots")

# Scatter plot of richness vs distance to road
p3 <- ggplot(merged_data, aes(x = DistanceToRoad, y = Richness, color = LandCoverClass)) +
  geom_point(size = 3) +
  theme_minimal() +
  labs(title = "Relationship between Richness and Distance to Road")

# Display plots
print(p1)

print(p2)

print(p3)

# Step 8: Final data output
cat("\n=== FINAL PROCESSED DATA ===\n")
## 
## === FINAL PROCESSED DATA ===
print(merged_data)
##    PlotID Richness DistanceToRoad LandCoverClass RasterValue
## 1       1        5    3.550912007            Low  46.4713774
## 2       2        5    0.003123867            Low  42.9776437
## 3       3        5    2.376582870            Low  50.2177311
## 4       4        5    1.100594426            Low   4.6454861
## 5       5        5    1.899082689            Low  87.6089286
## 6       6        5    3.063855016            Low  11.5582457
## 7       7        5    1.758989546            Low   2.4232898
## 8       8        5    0.555677122            Low  43.1102867
## 9       9        5    1.218097364            Low   0.5401072
## 10     10        5    3.340277937            Low   2.5526698
# Step 9: Session info for reproducibility
cat("\n=== SESSION INFORMATION ===\n")
## 
## === SESSION INFORMATION ===
sessionInfo()
## R version 4.3.3 (2024-02-29 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 11 x64 (build 22631)
## 
## Matrix products: default
## 
## 
## locale:
## [1] LC_COLLATE=English_United States.utf8 
## [2] LC_CTYPE=English_United States.utf8   
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.utf8    
## 
## time zone: Europe/Berlin
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] vegan_2.6-10   lattice_0.22-5 permute_0.9-8  terra_1.8-29   sf_1.0-20     
## [6] ggplot2_3.5.1  tidyr_1.3.1    dplyr_1.1.4   
## 
## loaded via a namespace (and not attached):
##  [1] sass_0.4.9         generics_0.1.3     class_7.3-22       KernSmooth_2.23-22
##  [5] digest_0.6.37      magrittr_2.0.3     evaluate_1.0.3     grid_4.3.3        
##  [9] fastmap_1.2.0      Matrix_1.6-5       jsonlite_1.9.1     e1071_1.7-16      
## [13] DBI_1.2.3          mgcv_1.9-1         purrr_1.0.4        viridisLite_0.4.2 
## [17] scales_1.3.0       codetools_0.2-19   jquerylib_0.1.4    cli_3.6.4         
## [21] rlang_1.1.5        units_0.8-7        splines_4.3.3      munsell_0.5.1     
## [25] withr_3.0.2        cachem_1.1.0       yaml_2.3.10        tools_4.3.3       
## [29] parallel_4.3.3     colorspace_2.1-1   vctrs_0.6.5        R6_2.6.1          
## [33] proxy_0.4-27       lifecycle_1.0.4    classInt_0.4-11    MASS_7.3-60.0.1   
## [37] cluster_2.1.6      pkgconfig_2.0.3    pillar_1.10.1      bslib_0.9.0       
## [41] gtable_0.3.6       glue_1.8.0         Rcpp_1.0.14        xfun_0.52         
## [45] tibble_3.2.1       tidyselect_1.2.1   rstudioapi_0.17.1  knitr_1.50        
## [49] farver_2.1.2       nlme_3.1-164       htmltools_0.5.8.1  labeling_0.4.3    
## [53] rmarkdown_2.29     compiler_4.3.3