In epidemiological studies of environmental noise, the general term time-weighted average is often used to describe energy-based averaging of noise levels over defined periods of time (European Union 2002). This approach is necessary because noise is measured in decibels (dB), a logarithmic scale, and cannot be averaged using arithmetic means (Brink and Haagsma 2024, p 25). Instead, energy-based (logarithmic) averaging is applied to accurately reflect cumulative exposure (Day–evening–night noise level 2025).
Why energy averaging is needed - not simple arithmetic mean
When calculating average noise levels over time, it’s critical to use energy-based rather than arithmetic averaging due to the logarithmic nature of the decibel scale. The difference becomes particularly important when there are large variations in noise levels.
The Logarithmic Nature of Sound
Sound intensity follows a logarithmic scale where each 10 dB increase represents a 10-fold increase in sound energy. Therefore, arithmetic averaging of decibel values leads to systematic underestimation of the true average noise exposure.
Note
An arithmetic average of 60 dB and 70 dB would give 65 dB, but this is incorrect because 70 dB has 10 times more sound energy than 60 dB. The correct energy-based average is approximately 67 dB.
Visual Demonstration of the Difference
Code
# Generate sample noise data with large variationsnoise_samples<-c(60, 70, 60, 60, 60, 60, 60, 60, 60, 60)# One very loud measurementsample_times<-1:length(noise_samples)# Calculate both types of averagesarithmetic_avg<-mean(noise_samples)energy_avg<-10*log10(mean(10^(noise_samples/10)))# Create a plot to visualize the differenceplot(sample_times, noise_samples, type ="o", pch =16, xlab ="Sample", ylab ="Noise Level (dB)", main ="Arithmetic vs. Energy-Based Averaging", ylim =c(min(noise_samples)-5, max(noise_samples)+5))# Add horizontal lines for both averagesabline(h =arithmetic_avg, col ="red", lwd =2, lty =2)abline(h =energy_avg, col ="blue", lwd =2, lty =2)# Add text labelslegend("topright", legend =c(paste("Arithmetic Mean:", round(arithmetic_avg, 1), "dB"),paste("Energy-Based Mean:", round(energy_avg, 1), "dB")), col =c("red", "blue"), lwd =2, lty =2)# Add explanation texttext(8, 62, "The arithmetic mean\nunderestimates the\ntrue noise exposure", col ="red")text(8, 68, "Energy-based averaging\ncorrectly accounts for\nthe loud event", col ="blue")
Energy Contribution by Noise Level
The following visualization demonstrates why energy-based averaging is necessary by showing the relative energy contribution of different noise levels:
Code
# Create a range of noise levels from 50 to 80 dBnoise_levels<-50:80energy_values<-10^(noise_levels/10)# Calculate relative energy compared to 50 dBrelative_energy<-energy_values/energy_values[1]# Create a data frame for plottingenergy_df<-data.frame( noise_level =noise_levels, relative_energy =relative_energy)# Create a bar plot of relative energybarplot(energy_df$relative_energy, names.arg =energy_df$noise_level, log ="y", # Use log scale for y-axis col =colorRampPalette(c("lightblue", "darkblue"))(length(noise_levels)), xlab ="Noise Level (dB)", ylab ="Relative Energy (compared to 50 dB, log scale)", main ="Relative Sound Energy by Noise Level")# Add grid linesgrid(nx =NA, ny =NULL, lty =2, col ="gray")# Add text labels for key valuestext(10, 10^3, "70 dB = 100× the energy of 50 dB", cex =0.9)text(25, 10^7, "80 dB = 1,000× the energy of 50 dB", cex =0.9)# Add subtitlemtext("Note logarithmic scale - energy increases by factor of 10 for every 10 dB", side =3, line =0.5)
The general formula for calculating a time-weighted average noise level (Lden) over ( n ) periods is:
where ( \(Lden_i\) ) is the noise level in decibels for the ( i )-th period.
Why we use \(Lden_i/10\) in the formula
The division by 10 in the time-weighted average formula is due to the mathematical relationship between decibels and the actual physical quantities they represent.
Converting back to energy domain: Decibel values are logarithmic representations of the underlying energy. The equation \(10^(Lden_i/10)\) converts the decibel value back to a quantity proportional to energy.
Mathematical basis: Sound levels in dB represent a 10-fold increase in energy for each 10 dB increase. The formula is derived from: \[L = 10 \cdot \log_{10}(I/I_0)\] where I is intensity and I₀ is a reference intensity.
Reversing the logarithm: To average energy correctly, we need to first convert from the logarithmic scale (dB) back to linear energy units using \(10^(Lden_i/10)\), then average these energy values, and finally convert back to dB using \(10*log_{10}()\).
Two specific applications of time-weighted averages are commonly used:
1. Monthly-Weighted Average
This approach is used to estimate an individual’s annual noise exposure when they have changed addresses during a given year. Since annual noise levels (e.g., Lden) differ by location, the final annual exposure is calculated by weighting each location’s Lden by the number of months the individual spent there. The resulting energy-weighted average accounts for partial-year exposures:
Example: - January to March (3 months) at Address A: Lden = 65 dB
- April to December (9 months) at Address B: Lden = 60 dB
# Monthly-Weighted Average - R examplemonths_a<-3# Months at address Alden_a<-65# Noise level at address A (dB)months_b<-9# Months at address Blden_b<-60# Noise level at address B (dB)total_months<-12# Calculate energy-weighted averagelden_year<-10*log10((1/total_months)*(months_a*10^(lden_a/10)+months_b*10^(lden_b/10)))cat("Annual Lden:", round(lden_year, 1), "dB\n")
Annual Lden: 61.9 dB
Code
# Load required packageslibrary(dplyr)library(knitr)# Create example dataset with address changesset.seed(123)address_data<-tibble( id =rep(1:3, each =3), # 3 individuals address =c("A", "B", "B", # Individual 1's addresses"C", "D", "D", # Individual 2's addresses"E", "E", "F"), # Individual 3's addresses start_month =c(1, 4, 11, # Start months for each address period1, 6, 9,1, 3, 8), end_month =c(3, 10, 12, # End months for each address period5, 8, 12,2, 7, 12), lden_db =c(65, 60, 56, # Noise levels at each address (dB)62, 59, 63,70, 68, 64))# Calculate months at each addressaddress_data<-address_data%>%mutate(months_at_address =end_month-start_month+1)# Display the datasetkable(address_data, caption ="Example address history dataset")
Example address history dataset
id
address
start_month
end_month
lden_db
months_at_address
1
A
1
3
65
3
1
B
4
10
60
7
1
B
11
12
56
2
2
C
1
5
62
5
2
D
6
8
59
3
2
D
9
12
63
4
3
E
1
2
70
2
3
E
3
7
68
5
3
F
8
12
64
5
Code
# Function to calculate energy-weighted averagecalc_energy_avg<-function(months, lden_values){total_months<-sum(months)weighted_sum<-sum(months*10^(lden_values/10))return(10*log10(weighted_sum/total_months))}# Calculate annual Lden for each individualannual_lden<-address_data%>%group_by(id)%>%summarize( annual_lden_db =calc_energy_avg(months_at_address, lden_db), total_months =sum(months_at_address), address_count =n_distinct(address))# Display resultskable(annual_lden, caption ="Annual time-weighted average noise exposure by individual", digits =1)
Annual time-weighted average noise exposure by individual
id
annual_lden_db
total_months
address_count
1
61.6
12
2
2
61.8
12
2
3
67.3
12
2
Code
# Visualize the resultspar(mar =c(5, 4, 3, 1))barplot(annual_lden$annual_lden_db, names.arg =paste("ID", annual_lden$id), ylab ="Annual Lden (dB)", main ="Time-weighted average noise exposure", col ="lightblue")
2. Multi-Year Energy Average
This method is used to compute preceding cumulative exposures over several years, typically used in studies assessing chronic exposure effects.
It is applied in two main ways:
Time-fixed: A fixed period (e.g., 5 years) prior to the baseline of the study. Each participant has one value.
Time-varying: A floating window (e.g., most recent 5 years before each time point in the follow-up). This results in a time-varying exposure variable, which is commonly used in Cox proportional hazards models.
Example for Time-Fixed 5-Year Average:
Lden values over the 5 years: 63, 64, 62, 61, 60 dB
# Multi-Year Energy Average - R example# Example data: yearly Lden values for 2010-2014 (preceding years for 2014)yearly_lden<-c(63, 64, 62, 61, 60)# Lden values for 5 consecutive yearsyears<-2010:2014# The years these values correspond ton_years<-length(yearly_lden)# Function for calculating energy-based averageenergy_avg<-function(decibels){10*log10(mean(10^(decibels/10)))}# Calculate the time-fixed 5-year average for baseline year 2014lden_5year<-energy_avg(yearly_lden)cat("5-year energy-average Lden for 2014 (based on 2010-2014):", round(lden_5year, 1), "dB\n\n")
5-year energy-average Lden for 2014 (based on 2010-2014): 62.2 dB
Code
# Time-varying example: 8 years of noise data (2010-2017)all_years<-2010:2017all_years_lden<-c(65, 64, 63, 62, 61, 60, 59, 58)# Noise values for 2010-2017names(all_years_lden)<-all_years# Label the data with years# Define follow-up years for which we want to calculate preceding exposurefollow_up_years<-2014:2017# Calculate 5-year preceding exposure for each follow-up yearcat("Time-varying preceding 5-year exposures:\n")
Time-varying preceding 5-year exposures:
Code
for(yearinfollow_up_years){# Define 5-year window preceding and including the current yearwindow_years<-(year-4):yearwindow_data<-all_years_lden[as.character(window_years)]# Calculate energy-average for this windowwindow_avg<-energy_avg(window_data)# Display result with window yearscat("Year", year, "exposure (based on", min(window_years), "-", year, "):", round(window_avg, 1), "dB\n")}
Year 2014 exposure (based on 2010 - 2014 ): 63.2 dB
Year 2015 exposure (based on 2011 - 2015 ): 62.2 dB
Year 2016 exposure (based on 2012 - 2016 ): 61.2 dB
Year 2017 exposure (based on 2013 - 2017 ): 60.2 dB
Code
# Visualize how the 5-year preceding window shiftspar(mar =c(5, 4, 4, 1))plot(all_years, all_years_lden, type ="o", pch =16, xlab ="Year", ylab ="Annual Lden (dB)", main ="Preceding 5-year windows for different follow-up years", xlim =c(min(all_years), max(all_years)), ylim =c(min(all_years_lden)-1, max(all_years_lden)+1))# Add colored segments for each 5-year windowcolors<-c("red", "blue", "green", "purple")for(iin1:length(follow_up_years)){year<-follow_up_years[i]window_years<-(year-4):yearwindow_data<-all_years_lden[as.character(window_years)]window_avg<-energy_avg(window_data)# Highlight the windowpoints(window_years, all_years_lden[as.character(window_years)], pch =16, col =colors[i], cex =1.2)lines(window_years, all_years_lden[as.character(window_years)], col =colors[i], lwd =2)# Add a horizontal line for the averagesegments(min(window_years), window_avg, max(window_years), window_avg, col =colors[i], lwd =2, lty =2)# Add labeltext(mean(window_years), window_avg+0.5, paste0("Avg: ", round(window_avg, 1), " dB"), col =colors[i])}# Add legendlegend("topright", legend =paste("Window for", follow_up_years), col =colors, lwd =2, pch =16)
Code
library(dplyr)library(tidyr)library(knitr)library(ggplot2)# Create longitudinal dataset with yearly noise exposuresset.seed(456)# Create noise exposure data for multiple participants over multiple yearsyears<-2010:2019# 10 years of dataparticipant_ids<-1:4# 4 participants# Create the datasetnoise_data<-expand.grid( id =participant_ids, year =years)%>%as_tibble()%>%# Generate realistic noise values with some trends and variationsgroup_by(id)%>%mutate(# Base noise level varies by participant base_noise =case_when(id==1~60,id==2~65,id==3~58,id==4~63),# Add yearly variations with slight decreasing trend yearly_variation =runif(n(), -3, 3)-0.2*(year-min(year)), lden =base_noise+yearly_variation)%>%ungroup()# Display a sample of the datakable(head(noise_data, 10), caption ="Sample of yearly noise exposure data by participant")
Sample of yearly noise exposure data by participant
id
year
base_noise
yearly_variation
lden
1
2010
60
-2.4626904
57.53731
2
2010
65
-0.7623245
64.23768
3
2010
58
-1.9207012
56.07930
4
2010
63
2.8455239
65.84552
1
2011
60
-1.9369261
58.06307
2
2011
65
-1.8925486
63.10745
3
2011
58
1.1304616
59.13046
4
2011
63
-1.8961756
61.10382
1
2012
60
0.9977316
60.99773
2
2012
65
1.1306302
66.13063
Code
# Function to calculate energy-weighted average over yearscalc_energy_avg_window<-function(noise_values){10*log10(mean(10^(noise_values/10)))}# 1. Time-fixed: Calculate 5-year average before baseline (2015)fixed_window_data<-noise_data%>%filter(year>=2010, year<=2014)%>%# 5-year window before 2015group_by(id)%>%summarize( baseline_year =2015, time_fixed_avg_lden =calc_energy_avg_window(lden), years_included =paste(min(year), "-", max(year)))# Display time-fixed resultskable(fixed_window_data, caption ="Time-fixed 5-year average noise exposure (2010-2014)", digits =1)
Time-fixed 5-year average noise exposure (2010-2014)
id
baseline_year
time_fixed_avg_lden
years_included
1
2015
60.1
2010 - 2014
2
2015
65.1
2010 - 2014
3
2015
58.5
2010 - 2014
4
2015
63.9
2010 - 2014
Code
# 2. Time-varying: Calculate for each year the preceding 5-year exposure window# For each year from 2014-2019, we calculate exposure from the 5 years before itfollow_up_years<-2014:2019# Create empty dataframe to store resultstime_varying_data<-data.frame()# For each follow-up year, calculate preceding 5-year averagefor(current_yearinfollow_up_years){# Define the 5-year window preceding the current yearwindow_start<-current_year-4window_end<-current_year# Filter data for this windowwindow_data<-noise_data%>%filter(year>=window_start, year<=window_end)%>%group_by(id)%>%summarize( year =current_year, # This is the year FOR WHICH we calculate preceding exposure preceding_5yr_lden =calc_energy_avg_window(lden), window_years =paste(window_start, "-", window_end))# Add to resultstime_varying_data<-bind_rows(time_varying_data, window_data)}# Arrange datatime_varying_data<-time_varying_data%>%arrange(id, year)# Display resultskable(head(time_varying_data, 8), caption ="Time-varying preceding 5-year noise exposure", digits =1)
Time-varying preceding 5-year noise exposure
id
year
preceding_5yr_lden
window_years
1
2014
60.1
2010 - 2014
1
2015
60.2
2011 - 2015
1
2016
60.0
2012 - 2016
1
2017
59.3
2013 - 2017
1
2018
58.2
2014 - 2018
1
2019
57.2
2015 - 2019
2
2014
65.1
2010 - 2014
2
2015
65.2
2011 - 2015
Code
# Visualize time-varying exposuresggplot(time_varying_data, aes(x =year, y =preceding_5yr_lden, color =factor(id), group =id))+geom_line(size =1)+geom_point()+labs( title ="Preceding 5-year average noise exposure by year", subtitle ="Each point shows exposure averaged over the 5 years preceding that year", x ="Year", y ="Preceding 5-year average Lden (dB)", color ="Participant ID")+theme_minimal()+scale_x_continuous(breaks =follow_up_years)
These methods ensure that noise exposure assessments reflect the true energy burden over time, accounting for address history and the chronicity of exposure.
Pressure-Based (20log10) vs Energy-Based Averaging (10log10)
A common point of confusion arises from the distinction between pressure-based and energy-based calculations when working with sound levels in decibels (dB). This is particularly relevant when discussing how to compute time-weighted or moving averages in noise exposure assessment.
The sound pressure level (SPL) is defined physically as:
where ( p ) is the measured sound pressure and ( p_0 = 20, ) is the reference pressure. This formula is used to convert raw sound pressure data into decibels.
In contrast, environmental noise indicators such as Lden and Lnight are already pressure-derived, energy-equivalent values expressed in dB. Once these values are available, we cannot average them arithmetically due to their logarithmic nature. Instead, we must convert them back into the energy domain before averaging:
This is known as energy-based averaging, and it is the correct approach for computing moving averages, multi-year exposures, or time-weighted values when using Lden or similar noise indicators.
Practical Interpretation
Although WHO’s Environmental Noise Guidelines for the European Region refer to Lden and Lnight as “sound pressure levels”, these are understood to be standardized, time-averaged energy equivalents based on SPL. The use of “20 log10” refers to the original calculation of SPL from pressure—not the method of averaging multiple Lden values.
Therefore, for epidemiological exposure assessment:
The “20 log10” formula is used in physical measurement of sound pressure (e.g., by sensors).
The “10 log10” formula is used in averaging exposure values like Lden that are already expressed in dB.
This distinction ensures accurate calculation of cumulative or time-varying noise exposure levels in health studies.
---title: "Time-Weighted Averages in Epidemiological Noise Studies"format: html: toc: true toc-depth: 4 toc-location: left code-fold: true code-tools: true code-link: true embed-resources: true theme: cosmo self-contained: true pdf: toc: true toc-depth: 4 number-sections: true colorlinks: true code-fold: show keep-tex: falseexecute: echo: true warning: false message: falsebibliography: references.bib---## General definitionsIn epidemiological studies of environmental noise, the general term *time-weighted average* is often used to describe energy-based averaging of noise levels over defined periods of time [@eu2002directive]. This approach is necessary because noise is measured in decibels (dB), a logarithmic scale, and cannot be averaged using arithmetic means [@brink2024determining, p 25]. Instead, energy-based (logarithmic) averaging is applied to accurately reflect cumulative exposure [@wikipediaLden].## Why energy averaging is needed - not simple arithmetic meanWhen calculating average noise levels over time, it's critical to use energy-based rather than arithmetic averaging due to the logarithmic nature of the decibel scale. The difference becomes particularly important when there are large variations in noise levels.### The Logarithmic Nature of SoundSound intensity follows a logarithmic scale where each 10 dB increase represents a 10-fold increase in sound energy. Therefore, arithmetic averaging of decibel values leads to systematic underestimation of the true average noise exposure.::: {.callout-note}An arithmetic average of 60 dB and 70 dB would give 65 dB, but this is incorrect because 70 dB has 10 times more sound energy than 60 dB. The correct energy-based average is approximately 67 dB.:::### Visual Demonstration of the Difference```{r}#| label: energy-vs-arithmetic#| fig-width: 10#| fig-height: 6# Generate sample noise data with large variationsnoise_samples <-c(60, 70, 60, 60, 60, 60, 60, 60, 60, 60) # One very loud measurementsample_times <-1:length(noise_samples)# Calculate both types of averagesarithmetic_avg <-mean(noise_samples)energy_avg <-10*log10(mean(10^(noise_samples/10)))# Create a plot to visualize the differenceplot(sample_times, noise_samples, type ="o", pch =16, xlab ="Sample", ylab ="Noise Level (dB)",main ="Arithmetic vs. Energy-Based Averaging",ylim =c(min(noise_samples) -5, max(noise_samples) +5))# Add horizontal lines for both averagesabline(h = arithmetic_avg, col ="red", lwd =2, lty =2)abline(h = energy_avg, col ="blue", lwd =2, lty =2)# Add text labelslegend("topright", legend =c(paste("Arithmetic Mean:", round(arithmetic_avg, 1), "dB"),paste("Energy-Based Mean:", round(energy_avg, 1), "dB")),col =c("red", "blue"), lwd =2, lty =2)# Add explanation texttext(8, 62, "The arithmetic mean\nunderestimates the\ntrue noise exposure", col ="red")text(8, 68, "Energy-based averaging\ncorrectly accounts for\nthe loud event", col ="blue")```### Energy Contribution by Noise LevelThe following visualization demonstrates why energy-based averaging is necessary by showing the relative energy contribution of different noise levels:```{r}#| label: energy-contribution#| fig-width: 10#| fig-height: 6# Create a range of noise levels from 50 to 80 dBnoise_levels <-50:80energy_values <-10^(noise_levels/10)# Calculate relative energy compared to 50 dBrelative_energy <- energy_values / energy_values[1]# Create a data frame for plottingenergy_df <-data.frame(noise_level = noise_levels,relative_energy = relative_energy)# Create a bar plot of relative energybarplot(energy_df$relative_energy, names.arg = energy_df$noise_level,log ="y", # Use log scale for y-axiscol =colorRampPalette(c("lightblue", "darkblue"))(length(noise_levels)),xlab ="Noise Level (dB)",ylab ="Relative Energy (compared to 50 dB, log scale)",main ="Relative Sound Energy by Noise Level")# Add grid linesgrid(nx =NA, ny =NULL, lty =2, col ="gray")# Add text labels for key valuestext(10, 10^3, "70 dB = 100× the energy of 50 dB", cex =0.9)text(25, 10^7, "80 dB = 1,000× the energy of 50 dB", cex =0.9)# Add subtitlemtext("Note logarithmic scale - energy increases by factor of 10 for every 10 dB", side =3, line =0.5)```The general formula for calculating a time-weighted average noise level (Lden) over \( n \) periods is:$$Lden_{avg} = 10 \cdot \log_{10} \left( \frac{1}{n} \sum_{i=1}^{n} 10^{Lden_i/10} \right)$$where \( $Lden_i$ \) is the noise level in decibels for the \( i \)-th period.::: {.callout-note collapse="true"}## Why we use $Lden_i/10$ in the formulaThe division by 10 in the time-weighted average formula is due to the mathematical relationship between decibels and the actual physical quantities they represent.1. **Converting back to energy domain**: Decibel values are logarithmic representations of the underlying energy. The equation $10^(Lden_i/10)$ converts the decibel value back to a quantity proportional to energy.2. **Mathematical basis**: Sound levels in dB represent a 10-fold increase in energy for each 10 dB increase. The formula is derived from: $$L = 10 \cdot \log_{10}(I/I_0)$$ where I is intensity and I₀ is a reference intensity.3. **Reversing the logarithm**: To average energy correctly, we need to first convert from the logarithmic scale (dB) back to linear energy units using $10^(Lden_i/10)$, then average these energy values, and finally convert back to dB using $10*log_{10}()$.:::Two specific applications of time-weighted averages are commonly used:### 1. Monthly-Weighted AverageThis approach is used to estimate an individual's *annual noise exposure* when they have changed addresses during a given year. Since annual noise levels (e.g., Lden) differ by location, the final annual exposure is calculated by weighting each location's Lden by the number of months the individual spent there. The resulting energy-weighted average accounts for partial-year exposures:**Example:**- January to March (3 months) at Address A: Lden = 65 dB - April to December (9 months) at Address B: Lden = 60 dB$$Lden_{year} = 10 \cdot \log_{10} \left( \frac{1}{12} \left(3 \cdot 10^{65/10} + 9 \cdot 10^{60/10} \right) \right) \approx 61.9\, \text{dB}$$::: {.panel-tabset}## Single Estimate```{r}# Monthly-Weighted Average - R examplemonths_a <-3# Months at address Alden_a <-65# Noise level at address A (dB)months_b <-9# Months at address Blden_b <-60# Noise level at address B (dB)total_months <-12# Calculate energy-weighted averagelden_year <-10*log10((1/total_months) * (months_a *10^(lden_a/10) + months_b *10^(lden_b/10)))cat("Annual Lden:", round(lden_year, 1), "dB\n")```## Dataset estimates```{r}#| echo: true#| eval: true#| warning: false# Load required packageslibrary(dplyr)library(knitr)# Create example dataset with address changesset.seed(123)address_data <-tibble(id =rep(1:3, each =3), # 3 individualsaddress =c("A", "B", "B", # Individual 1's addresses"C", "D", "D", # Individual 2's addresses"E", "E", "F"), # Individual 3's addressesstart_month =c(1, 4, 11, # Start months for each address period1, 6, 9,1, 3, 8),end_month =c(3, 10, 12, # End months for each address period5, 8, 12,2, 7, 12),lden_db =c(65, 60, 56, # Noise levels at each address (dB)62, 59, 63,70, 68, 64))# Calculate months at each addressaddress_data <- address_data %>%mutate(months_at_address = end_month - start_month +1)# Display the datasetkable(address_data, caption ="Example address history dataset")# Function to calculate energy-weighted averagecalc_energy_avg <-function(months, lden_values) { total_months <-sum(months) weighted_sum <-sum(months *10^(lden_values/10))return(10*log10(weighted_sum / total_months))}# Calculate annual Lden for each individualannual_lden <- address_data %>%group_by(id) %>%summarize(annual_lden_db =calc_energy_avg(months_at_address, lden_db),total_months =sum(months_at_address),address_count =n_distinct(address) )# Display resultskable(annual_lden, caption ="Annual time-weighted average noise exposure by individual",digits =1)# Visualize the resultspar(mar =c(5, 4, 3, 1))barplot(annual_lden$annual_lden_db, names.arg =paste("ID", annual_lden$id),ylab ="Annual Lden (dB)",main ="Time-weighted average noise exposure",col ="lightblue")```:::### 2. Multi-Year Energy AverageThis method is used to compute *preceding cumulative exposures* over several years, typically used in studies assessing chronic exposure effects.It is applied in two main ways:- **Time-fixed**: A fixed period (e.g., 5 years) prior to the baseline of the study. Each participant has one value.- **Time-varying**: A floating window (e.g., most recent 5 years before each time point in the follow-up). This results in a time-varying exposure variable, which is commonly used in **Cox proportional hazards models**.**Example for Time-Fixed 5-Year Average:** Lden values over the 5 years: 63, 64, 62, 61, 60 dB$$Lden_{5years} = 10 \cdot \log_{10} \left( \frac{1}{5} (10^{6.3} + 10^{6.4} + 10^{6.2} + 10^{6.1} + 10^{6.0}) \right) \approx 62.2\, \text{dB}$$::: {.panel-tabset}### Single Estimate```{r}# Multi-Year Energy Average - R example# Example data: yearly Lden values for 2010-2014 (preceding years for 2014)yearly_lden <-c(63, 64, 62, 61, 60) # Lden values for 5 consecutive yearsyears <-2010:2014# The years these values correspond ton_years <-length(yearly_lden)# Function for calculating energy-based averageenergy_avg <-function(decibels) {10*log10(mean(10^(decibels/10)))}# Calculate the time-fixed 5-year average for baseline year 2014lden_5year <-energy_avg(yearly_lden)cat("5-year energy-average Lden for 2014 (based on 2010-2014):", round(lden_5year, 1), "dB\n\n")# Time-varying example: 8 years of noise data (2010-2017)all_years <-2010:2017all_years_lden <-c(65, 64, 63, 62, 61, 60, 59, 58) # Noise values for 2010-2017names(all_years_lden) <- all_years # Label the data with years# Define follow-up years for which we want to calculate preceding exposurefollow_up_years <-2014:2017# Calculate 5-year preceding exposure for each follow-up yearcat("Time-varying preceding 5-year exposures:\n")for(year in follow_up_years) {# Define 5-year window preceding and including the current year window_years <- (year-4):year window_data <- all_years_lden[as.character(window_years)]# Calculate energy-average for this window window_avg <-energy_avg(window_data)# Display result with window yearscat("Year", year, "exposure (based on", min(window_years), "-", year, "):", round(window_avg, 1), "dB\n")}# Visualize how the 5-year preceding window shiftspar(mar =c(5, 4, 4, 1))plot(all_years, all_years_lden, type ="o", pch =16, xlab ="Year", ylab ="Annual Lden (dB)",main ="Preceding 5-year windows for different follow-up years",xlim =c(min(all_years), max(all_years)), ylim =c(min(all_years_lden)-1, max(all_years_lden)+1))# Add colored segments for each 5-year windowcolors <-c("red", "blue", "green", "purple")for(i in1:length(follow_up_years)) { year <- follow_up_years[i] window_years <- (year-4):year window_data <- all_years_lden[as.character(window_years)] window_avg <-energy_avg(window_data)# Highlight the windowpoints(window_years, all_years_lden[as.character(window_years)], pch =16, col = colors[i], cex =1.2)lines(window_years, all_years_lden[as.character(window_years)], col = colors[i], lwd =2)# Add a horizontal line for the averagesegments(min(window_years), window_avg, max(window_years), window_avg, col = colors[i], lwd =2, lty =2)# Add labeltext(mean(window_years), window_avg +0.5, paste0("Avg: ", round(window_avg, 1), " dB"), col = colors[i])}# Add legendlegend("topright", legend =paste("Window for", follow_up_years), col = colors, lwd =2, pch =16)```### Dataset estimates```{r}#| echo: true#| eval: true#| warning: falselibrary(dplyr)library(tidyr)library(knitr)library(ggplot2)# Create longitudinal dataset with yearly noise exposuresset.seed(456)# Create noise exposure data for multiple participants over multiple yearsyears <-2010:2019# 10 years of dataparticipant_ids <-1:4# 4 participants# Create the datasetnoise_data <-expand.grid(id = participant_ids,year = years) %>%as_tibble() %>%# Generate realistic noise values with some trends and variationsgroup_by(id) %>%mutate(# Base noise level varies by participantbase_noise =case_when( id ==1~60, id ==2~65, id ==3~58, id ==4~63 ),# Add yearly variations with slight decreasing trendyearly_variation =runif(n(), -3, 3) -0.2* (year -min(year)),lden = base_noise + yearly_variation ) %>%ungroup()# Display a sample of the datakable(head(noise_data, 10), caption ="Sample of yearly noise exposure data by participant")# Function to calculate energy-weighted average over yearscalc_energy_avg_window <-function(noise_values) {10*log10(mean(10^(noise_values/10)))}# 1. Time-fixed: Calculate 5-year average before baseline (2015)fixed_window_data <- noise_data %>%filter(year >=2010, year <=2014) %>%# 5-year window before 2015group_by(id) %>%summarize(baseline_year =2015,time_fixed_avg_lden =calc_energy_avg_window(lden),years_included =paste(min(year), "-", max(year)) )# Display time-fixed resultskable(fixed_window_data, caption ="Time-fixed 5-year average noise exposure (2010-2014)",digits =1)# 2. Time-varying: Calculate for each year the preceding 5-year exposure window# For each year from 2014-2019, we calculate exposure from the 5 years before itfollow_up_years <-2014:2019# Create empty dataframe to store resultstime_varying_data <-data.frame()# For each follow-up year, calculate preceding 5-year averagefor (current_year in follow_up_years) {# Define the 5-year window preceding the current year window_start <- current_year -4 window_end <- current_year# Filter data for this window window_data <- noise_data %>%filter(year >= window_start, year <= window_end) %>%group_by(id) %>%summarize(year = current_year, # This is the year FOR WHICH we calculate preceding exposurepreceding_5yr_lden =calc_energy_avg_window(lden),window_years =paste(window_start, "-", window_end) )# Add to results time_varying_data <-bind_rows(time_varying_data, window_data)}# Arrange datatime_varying_data <- time_varying_data %>%arrange(id, year)# Display resultskable(head(time_varying_data, 8), caption ="Time-varying preceding 5-year noise exposure",digits =1)# Visualize time-varying exposuresggplot(time_varying_data, aes(x = year, y = preceding_5yr_lden, color =factor(id), group = id)) +geom_line(size =1) +geom_point() +labs(title ="Preceding 5-year average noise exposure by year",subtitle ="Each point shows exposure averaged over the 5 years preceding that year",x ="Year",y ="Preceding 5-year average Lden (dB)",color ="Participant ID" ) +theme_minimal() +scale_x_continuous(breaks = follow_up_years)```:::These methods ensure that noise exposure assessments reflect the true energy burden over time, accounting for address history and the chronicity of exposure.## Pressure-Based (20*log10) vs Energy-Based Averaging (10*log10)A common point of confusion arises from the distinction between *pressure-based* and *energy-based* calculations when working with sound levels in decibels (dB). This is particularly relevant when discussing how to compute time-weighted or moving averages in noise exposure assessment.The **sound pressure level (SPL)** is defined physically as:$$\text{SPL} = 20 \cdot \log_{10} \left( \frac{p}{p_0} \right)$$where \( p \) is the measured sound pressure and \( p_0 = 20\,\mu\text{Pa} \) is the reference pressure. This formula is used to convert raw sound pressure data into decibels.In contrast, environmental noise indicators such as **Lden** and **Lnight** are already pressure-derived, energy-equivalent values expressed in dB. Once these values are available, **we cannot average them arithmetically** due to their logarithmic nature. Instead, we must convert them back into the energy domain before averaging:$$L_{avg} = 10 \cdot \log_{10} \left( \frac{1}{n} \sum_{i=1}^{n} 10^{L_i/10} \right)$$This is known as **energy-based averaging**, and it is the correct approach for computing moving averages, multi-year exposures, or time-weighted values when using Lden or similar noise indicators.### Practical InterpretationAlthough WHO's *Environmental Noise Guidelines for the European Region* refer to Lden and Lnight as *"sound pressure levels"*, these are understood to be standardized, time-averaged energy equivalents based on SPL. The use of "20 log10" refers to the original calculation of SPL from pressure—not the method of averaging multiple Lden values.Therefore, for epidemiological exposure assessment:- The **"20 log10" formula** is used in **physical measurement** of sound pressure (**e.g., by sensors)**.- The **"10 log10" formula** is used in **averaging exposure values** like Lden that are **already expressed** in dB.This distinction ensures accurate calculation of cumulative or time-varying noise exposure levels in health studies.