If age heaping is much worse on 0's than on 5's then even counts in 5-year age bins can preserve a sawtooth pattern. Most graduation techniques translate the zig-zag/sawtooth pattern to a wave pattern. It is not typically desired. This method redistributes counts 'from' every second 5-year age group in a specified range 'to' the adjacent age groups. How much to redistribute depends on a detection of roughness in the 5-year binned data, which follows the formulas recommended by Feeney.

  ageMin = 40,
  ageMax = max(Age) - max(Age)%%10 - 5



numeric vector of (presumably) counts in 5-year age groups.


integer vector of age group lower bounds.


logical. Whether or not the top age group is open. Default TRUE.


integer. Lower age bound to adjust values.


integer. Upper age bound to adjust values.


numeric vector of smoothed counts in 5-year age groups.


Determining the degree to redistribute population counts is an optimization problem, so this function has two auxiliary functions, p_zigzag(), which redistributes counts according to a set of specified proportions p, and zigzag_min() which is the function minimized to determine the optimal vector of p. If data is not in 5-year age groups then it is grouped as necessary (unless abridged, in which case grouping is preserved). Only ages >= ageMin and <= ageMax will be adjusted. ageMax is inclusive and interpreted as the lower bound of the highest age group to include. The number of 5-year age groups adjusted must be odd, and ageMax may be reduced internally without warning in order to force this condition. The open age group is excluded from adjustment. This function also has a wrapper smooth_age_5_zigzag(), called by smooth_age_5().


Feeney, G. 2013 "Removing "Zigzag" from Age Data," http://demographer.com/white-papers/2013-removing-zigzag-from-age-data/


Age <- c(0,1,seq(5,90,by=5)) # defaults zz1 <- smooth_age_5_zigzag_inner(dth5_zigzag, Age, OAG = TRUE, ageMin = 40, ageMax = 80) # shift age range up by 5 zz2 <- smooth_age_5_zigzag_inner(dth5_zigzag, Age, OAG = TRUE, ageMin = 45, ageMax = 85) if (FALSE) { plot(Age, dth5_zigzag,pch = 16) lines(Age,zz1,col="red",lty=1) lines(Age,zz2,col="blue",lty=1) # even smoother: lines(Age,(zz2+zz1)/2,col="green",lty=1) legend("bottom",pch=c(16,NA,NA,NA), lty=c(NA,1,1,1), col = c("black","red","blue","green"), legend = c("Original","40-80","45-85","avg")) }