This chunk imports the raw LMD dataset from the Excel file into R for further cleaning and processing.
library(readxl)
LMD = read_excel("C:/Users/Samsung/OneDrive/Documentos/2 - Material de trabalho/7 - Projetos/7.1 Pesquisa/LMD/LMD Raw Data 06-24 apr 2026.xlsx", sheet = "R table")
head(LMD)
This chunk separates the calibration records and calculates the mean, standard deviation, minimum, and maximum ppm values.
# Separate calibration data
Calibration = subset(LMD, treatment == "Calibration")
# Calibration summary
Calibration_summary = data.frame(
Mean = mean(Calibration$ppm.m, na.rm = TRUE),
SD = sd(Calibration$ppm.m, na.rm = TRUE),
Min = min(Calibration$ppm.m, na.rm = TRUE),
Max = max(Calibration$ppm.m, na.rm = TRUE))
head(Calibration_summary)
writexl::write_xlsx(Calibration, "1 - Calibration Data.xlsx")
This chunk removes all rows identified as Calibration, retaining only the measurement data.
LMD = subset(LMD, treatment != "Calibration" | is.na(treatment))
print(LMD)
## # A tibble: 621,871 Ă— 9
## SerialNo SessionName treatment DateTime ppm.m `Distance(m)`
## <dbl> <dbl> <chr> <dttm> <dbl> <dbl>
## 1 4647 865 G2 2026-04-06 11:00:35 0 1
## 2 4647 865 G2 2026-04-06 11:00:35 0 1
## 3 4647 865 G2 2026-04-06 11:00:35 0 1
## 4 4647 865 G2 2026-04-06 11:00:35 0 1
## 5 4647 865 G2 2026-04-06 11:00:35 0 1
## 6 4647 865 G2 2026-04-06 11:00:35 0 1
## 7 4647 865 G2 2026-04-06 11:00:35 0 1
## 8 4647 865 G2 2026-04-06 11:00:35 0 1
## 9 4647 865 G2 2026-04-06 11:00:35 0 1
## 10 4647 865 G2 2026-04-06 11:00:34 0 1
## # ℹ 621,861 more rows
## # ℹ 3 more variables: Latitude <dbl>, Longitude <dbl>, Media <chr>
writexl::write_xlsx(LMD, "2 - Main data without Calibration.xlsx")
This chunk identifies measurements taken more than 5 m away as background.
Background = subset(LMD, `Distance(m)` > 5)
head(Background)
writexl::write_xlsx(Background, "3 - Background.xlsx")
This chunk identifies ppm measurements below the 2.5th percentile or above the 97.5th percentile as potential outliers.
# Define the central 95% range
Lower_limit = quantile(Background$ppm.m, 0.025, na.rm = TRUE)
Upper_limit = quantile(Background$ppm.m, 0.975, na.rm = TRUE)
Lower_limit
## 2.5%
## 0
Upper_limit
## 97.5%
## 34.65
Outliers = subset(Background, ppm.m < Lower_limit | ppm.m > Upper_limit)
nrow(Outliers)
## [1] 151
head(Outliers)
Background_clean = subset(Background,ppm.m >= Lower_limit & ppm.m <= Upper_limit)
print(Background_clean)
## # A tibble: 5,864 Ă— 9
## SerialNo SessionName treatment DateTime ppm.m `Distance(m)`
## <dbl> <dbl> <chr> <dttm> <dbl> <dbl>
## 1 4647 865 G2 2026-04-06 10:58:43 0 20
## 2 4647 865 G2 2026-04-06 10:58:43 0 20
## 3 4647 865 G2 2026-04-06 10:58:43 0 20
## 4 4647 865 G2 2026-04-06 10:58:43 0 20
## 5 4647 865 G2 2026-04-06 10:58:42 0 20
## 6 4647 865 G2 2026-04-06 10:58:42 0 20
## 7 4647 865 G2 2026-04-06 10:58:42 0 20
## 8 4647 865 G2 2026-04-06 10:58:42 0 20
## 9 4647 865 G2 2026-04-06 10:58:42 0 20
## 10 4647 865 G2 2026-04-06 10:58:42 0 20
## # ℹ 5,854 more rows
## # ℹ 3 more variables: Latitude <dbl>, Longitude <dbl>, Media <chr>
writexl::write_xlsx(Background_clean, "4 - Background_clean.xlsx")
This chunk summarizes their ppm values.
# Background summary
Background_summary = data.frame(
Mean = mean(Background_clean$ppm.m, na.rm = TRUE),
SD = sd(Background_clean$ppm.m, na.rm = TRUE),
Min = min(Background_clean$ppm.m, na.rm = TRUE),
Max = max(Background_clean$ppm.m, na.rm = TRUE))
Background_summary
This chunk counts the number of LMD measurements recorded each second and calculates the mean, standard deviation, minimum, and maximum measurement frequency.The obtained average can also be interpreted approximately as the sampling frequency in Hz.
Animal_data = subset(LMD, `Distance(m)` < 5)
View(Animal_data)
writexl::write_xlsx(Animal_data, "5 - Animal_data.xlsx")
# Count the number of measurements per second
Measurements_per_second = as.data.frame(table(Animal_data$DateTime))
Measurements_per_second
names(Measurements_per_second) = c("DateTime", "Measurements")
# Summary of measurements per second
Measurement_rate_summary = data.frame(
Mean = mean(Measurements_per_second$Measurements),
SD = sd(Measurements_per_second$Measurements),
Min = min(Measurements_per_second$Measurements),
Max = max(Measurements_per_second$Measurements))
Measurements_per_second[Measurements_per_second$Measurements == max(Measurements_per_second$Measurements),]
Measurement_rate_summary
This chunk averages all ppm measurements recorded within each second, resulting in one ppm value per second.
names(Animal_data)
## [1] "SerialNo" "SessionName" "treatment" "DateTime" "ppm.m"
## [6] "Distance(m)" "Latitude" "Longitude" "Media"
LMD_second = aggregate(ppm.m ~ SerialNo + SessionName + treatment + DateTime + `Distance(m)`,
data = Animal_data, FUN = mean,na.rm = TRUE)
View(LMD_second)
writexl::write_xlsx(LMD_second, "6 - Animal_data_second.xlsx")
This chunk creates the measurement date and period, assigns a sequential number to each unique second within each animal, day, and period, and calculates the hours elapsed since feeding at 09:00.
# Measurement date
LMD_second$Day = as.Date(LMD_second$DateTime)
# Morning or afternoon
LMD_second$Period = ifelse(as.integer(format(LMD_second$DateTime, "%H")) < 12,"Morning","Afternoon")
LMD_second = LMD_second[order(LMD_second$Day,LMD_second$SessionName,LMD_second$Period,LMD_second$DateTime),]
head(LMD_second)
# Sequential unique seconds for each animal, day and period
LMD_second$Second = ave(as.numeric(LMD_second$DateTime),
LMD_second$SessionName,
LMD_second$Day,
LMD_second$Period,
FUN = function(x) match(x, unique(x)))
head(LMD_second)
# Hours after feeding at 09:00
LMD_second$Hours_after_feeding = (
as.numeric(format(LMD_second$DateTime, "%H")) +
as.numeric(format(LMD_second$DateTime, "%M")) / 60 +
as.numeric(format(LMD_second$DateTime, "%S")) / 3600) - 9
View(LMD_second)
writexl::write_xlsx(LMD_second, "7 - Animal_data_Time-Based_Variables.xlsx")
This chunk calculates the total number of measurement seconds for each animal, day, and period, and summarizes the mean, standard deviation, minimum, and maximum duration.
# Total measurement seconds for each animal, day and period
Seconds_summary = aggregate(Second ~ SessionName + Day + Period,data = LMD_second,FUN = max)
# Summary statistics
Second_statistics = data.frame(
Mean = mean(Seconds_summary$Second, na.rm = TRUE),
SD = sd(Seconds_summary$Second, na.rm = TRUE),
Min = min(Seconds_summary$Second, na.rm = TRUE),
Max = max(Seconds_summary$Second, na.rm = TRUE))
Second_statistics
Seconds_summary[Seconds_summary$Second == max(Seconds_summary$Second),]
This chunk plots ppm measurements over measurement time and adds an upper threshold defined as the overall mean plus three standard deviations to visually identify unusually high values.
# Calculate mean and SD
Mean_ppm = mean(LMD_second$ppm.m, na.rm = TRUE)
SD_ppm = sd(LMD_second$ppm.m, na.rm = TRUE)
# Upper 3-SD threshold
Upper_3SD = Mean_ppm + (3 * SD_ppm)
library(ggplot2)
# Plot
ggplot(LMD_second, aes(x = Second, y = ppm.m)) +
geom_point(size = 0.7, alpha = 0.5) +
geom_hline(yintercept = Upper_3SD,color = "red",linetype = "dashed",linewidth = 0.5) +
coord_cartesian(xlim = c(0, 600)) +
labs(x = "Measurement time (s)", y = "ppm", title = "ppm Measurements over Time") +
theme_classic()
# Display threshold
Upper_3SD
## [1] 301.9657
This chunk identifies and removes ppm measurements above the mean plus three standard deviations.
# Identify values above the 3-SD threshold
Outliers_3SD = subset(LMD_second,ppm.m > Upper_3SD)
Outliers_3SD
# Remove values above the threshold
LMD_3SD = subset(LMD_second, ppm.m <= Upper_3SD | is.na(ppm.m))
LMD_3SD
writexl::write_xlsx(LMD_3SD, "8 - Animal_data_LMD_Clean_3SD.xlsx")
This chunk corrects the ppm measurements by subtracting the mean background concentration from each observation. Negative corrected values are removed, and the remaining ppm values are rounded to two decimal places.
# Subtract the mean background value
LMD_3SD$ppm.m = LMD_3SD$ppm.m - Background_summary$Mean
# Remove negative values
LMD_second_corr = subset(LMD_3SD, ppm.m >= 0)
LMD_second_corr$ppm.m = round(LMD_second_corr$ppm.m, 2)
View(LMD_second_corr)
writexl::write_xlsx(LMD_second_corr, "9 - LMD_clean_second_background_correction.xlsx")
This chunk evaluates the distribution of ppm measurements using the Shapiro–Wilk test, skewness, kurtosis, and a Q-Q plot.
# Shapiro-Wilk test
Shapiro = shapiro.test(sample(LMD_second_corr$ppm.m, min(5000, length(LMD_second_corr$ppm.m))))
Shapiro
##
## Shapiro-Wilk normality test
##
## data: sample(LMD_second_corr$ppm.m, min(5000, length(LMD_second_corr$ppm.m)))
## W = 0.54353, p-value < 2.2e-16
# Skewness and kurtosis
Skewness = e1071::skewness(LMD_second_corr$ppm.m, type = 2)
Kurtosis = e1071::kurtosis(LMD_second_corr$ppm.m, type = 2)
# Summary
Distribution_summary = data.frame(n = length(LMD_second_corr$ppm.m),
Shapiro_W = unname(Shapiro$statistic),
Shapiro_p = Shapiro$p.value,
Skewness = Skewness,
Kurtosis = Kurtosis)
Distribution_summary
# Q-Q plot
qqnorm(LMD_second_corr$ppm.m,main = "Q-Q Plot of ppm Measurements",
xlab = "Theoretical Quantiles",
ylab = "Sample Quantiles")
# Histogram of ppm measurements
hist(LMD_second_corr$ppm.m, main = "Distribution of ppm Measurements", xlab = "ppm", ylab = "Frequency")