Set Up

Data Preparation

# Keep only the first time each Tree_ID appears
first_measurements <- data %>%
  group_by(Tree_ID) %>%
  slice(1:n_distinct(Method)) %>%  # keep one row per method per first tree occurrence
  ungroup()

# Pivot so each method's TH and DBH are in separate columns
wide_data <- first_measurements %>%
  select(Tree_ID, Method, TH, DBH) %>%
  tidyr::pivot_wider(
    names_from = Method,
    values_from = c(TH, DBH),
    values_fn = list(TH = ~ first(.), DBH = ~ first(.))
  )

# Make sure columns are numeric
wide_data <- wide_data %>%
  mutate(across(c(TH_Geoslam, TH_Reference, TH_RTC360, TH_BLK360,
                  TH_Apple,
                  DBH_Geoslam, DBH_Reference, DBH_RTC360,
                  DBH_BLK360, DBH_Apple),
                ~ as.numeric(.)))

Data Analysis

# Calculate accuracy (difference) for each method compared to Reference
accuracy <- wide_data %>%
  mutate(
    Geoslam_TH_Error = TH_Geoslam - TH_Reference,
    RTC360_TH_Error  = TH_RTC360 - TH_Reference,
    BLK360_TH_Error  = TH_BLK360 - TH_Reference,
    apple_TH_Error  = TH_Apple - TH_Reference,
    Geoslam_DBH_Error = DBH_Geoslam - DBH_Reference,
    RTC360_DBH_Error  = DBH_RTC360 - DBH_Reference,
    BLK360_DBH_Error  = DBH_BLK360 - DBH_Reference,
    apple_DBH_Error  = DBH_Apple - DBH_Reference
  )

# Summary stats for accuracy
accuracy_summary <- accuracy %>%
  summarise(
    across(ends_with("_Error"),
           list(mean = ~mean(.x, na.rm = TRUE),
                sd = ~sd(.x, na.rm = TRUE)))
  )

accuracy_summary
## # A tibble: 1 × 16
##   Geoslam_TH_Error_mean Geoslam_TH_Error_sd RTC360_TH_Error_mean
##                   <dbl>               <dbl>                <dbl>
## 1                -0.399                1.59               -0.448
## # ℹ 13 more variables: RTC360_TH_Error_sd <dbl>, BLK360_TH_Error_mean <dbl>,
## #   BLK360_TH_Error_sd <dbl>, apple_TH_Error_mean <dbl>,
## #   apple_TH_Error_sd <dbl>, Geoslam_DBH_Error_mean <dbl>,
## #   Geoslam_DBH_Error_sd <dbl>, RTC360_DBH_Error_mean <dbl>,
## #   RTC360_DBH_Error_sd <dbl>, BLK360_DBH_Error_mean <dbl>,
## #   BLK360_DBH_Error_sd <dbl>, apple_DBH_Error_mean <dbl>,
## #   apple_DBH_Error_sd <dbl>

Results Table

Summary of Lidar Accuracy
Geoslam_TH_Error_mean Geoslam_TH_Error_sd RTC360_TH_Error_mean RTC360_TH_Error_sd BLK360_TH_Error_mean BLK360_TH_Error_sd apple_TH_Error_mean apple_TH_Error_sd Geoslam_DBH_Error_mean Geoslam_DBH_Error_sd RTC360_DBH_Error_mean RTC360_DBH_Error_sd BLK360_DBH_Error_mean BLK360_DBH_Error_sd apple_DBH_Error_mean apple_DBH_Error_sd
-0.4 1.59 -0.45 0.56 -0.83 1.29 -24.79 3.02 -0.01 0.12 -0.01 0.04 0 0.05 0.05 0.24

Accuracy of each method for DBH

ggplot(accuracy, aes(x = Tree_ID)) +
  geom_point(aes(y = Geoslam_DBH_Error, color = "Geoslam"), size = 2) +
  geom_point(aes(y = RTC360_DBH_Error, color = "RTC360"), size = 2) +
  geom_point(aes(y = BLK360_DBH_Error, color = "BLK360"), size = 2) + geom_point(aes(y = apple_DBH_Error, color = "Apple"), size = 2) +
  labs(
    title = "DBH Accuracy Comparison",
    x = "Tree ID",
    y = "DBH Error (m)",
    color = "Method"
  ) +
  theme_minimal() +
  scale_color_manual(values = c("Geoslam" = "blue", "RTC360" = "red", "BLK360" = "green", "Apple" = "orange")) +
theme(
    plot.title = element_text(size = 20, face = "bold"),
    axis.title.x = element_text(size = 16),
    axis.title.y = element_text(size = 16),
    axis.text.x = element_text(size = 14),
    axis.text.y = element_text(size = 14),
    legend.title = element_text(size = 16),
    legend.text  = element_text(size = 14)
  )
## Warning: Removed 8 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 4 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 6 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 25 rows containing missing values or values outside the scale range
## (`geom_point()`).

Plotting the accuracy of each method for TH

ggplot(accuracy, aes(x = Tree_ID)) +
  geom_point(aes(y = Geoslam_TH_Error, color = "Geoslam"), size = 2) +
  geom_point(aes(y = RTC360_TH_Error, color = "RTC360"), size = 2) +
  geom_point(aes(y = BLK360_TH_Error, color = "BLK360"), size = 2) + geom_point(aes(y = apple_TH_Error, color = "Apple"), size = 2) +
  labs(
    title = "TH Accuracy Comparison",
    x = "Tree ID",
    y = "TH Error (m)",
    color = "Method"
  ) +
  theme_minimal() +
  scale_color_manual(values = c("Geoslam" = "blue", "RTC360" = "red", "BLK360" = "green", "Apple" = "orange")) +
  theme(
    plot.title = element_text(size = 20, face = "bold"),
    axis.title.x = element_text(size = 16),
    axis.title.y = element_text(size = 16),
    axis.text.x = element_text(size = 14),
    axis.text.y = element_text(size = 14),
    legend.title = element_text(size = 16),
    legend.text  = element_text(size = 14)
  )
## Warning: Removed 8 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 4 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 6 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 25 rows containing missing values or values outside the scale range
## (`geom_point()`).