The FredsEmpirical formula is a linear function of route distance, cumulative elevation gain and terrain type that estimates the backpacking travel time. This document provides the data sources and statistical regression model for the FredsEmpirical formula.
$\boldsymbol{x}_{i},i=0,\ldots,n$ Successive positions listed in test route.
$d\left(\boldsymbol{x},\boldsymbol{y}\right)$ A function that returns the great circle distance between two positions.
$C_{i},i=1,\ldots,n$ A list of binary variables, $C_{i}=1$ if the corresponding segment is over cross country, and $C_{i}=0$ is on trail.
$S=\sum_{i=1}^{n}d\left(\boldsymbol{x}_{i},\boldsymbol{x}_{i-1}\right)\left(1-C_{i}\right)$ Total estimated trail distance of test route.
$R=\sum_{i=1}^{n}d\left(\boldsymbol{x}_{i},\boldsymbol{x}_{i-1}\right)C_{i}$ Total estimated cross country distance of test route.
$z_{i},i=0,\ldots,n$ Elevation estimates for each position in test route.
$Z=\sum_{i=1}^{n}\max\left(0,z_{i}-z_{i-1}\right)$ Cumulative elevation gain for test route.
$\tau_{0},\tau_{f}$ Time stamps of the start and finish times for actual backpack of test route.
$T=\tau_{f}-\tau_{0}$ Meaured travel time for test route.
I collected travel time, trail distance, cross country distance and elevation gain for 31 days of backpacking spread over four backpacking trips. In this paper, there is a lengthy discussion on what makes these variables good predictors of travel time.
Here, I just plot the data. A table of the data appears at the end of this article.
# plot the data as scatter diagrams, total hours vs miles on trail, miles on CC, elevation gain
import matplotlib.pyplot as plt
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "last"
miles_trail = getMilesTrail()
miles_CC = getMilesCC()
elevation_gain_feet = getElevGainFeet()
total_hours = getTotalHours()
fig = plt.figure(1, figsize=(8,8))
ax0 = fig.add_subplot(211)
ax0.plot(miles_trail, total_hours, linestyle='None', marker=u'D', color='dodgerblue', label='Trail');
ax0.plot(miles_CC, total_hours, linestyle='None', marker=u'D', color='coral', label='Cross country');
ax0.legend()
ax0.grid()
ax0.set_xlabel('Route miles');
ax0.set_ylabel('Total hours');
ax0 = fig.add_subplot(212)
ax0.plot(elevation_gain_feet, total_hours, linestyle='None', marker=u'D', color='dodgerblue');
ax0.grid()
ax0.set_xlabel('Elevation gain (feet)');
ax0.set_ylabel('Total hours');
Here are scatter plots of total travel hours ($T$) versus each of the three variables route miles on trail ($S$), route miles cross country ($R$) and elevation gain ($Z$). We can see a relationship between all three variables and total travel time. We also see that cross country miles has a stronger effect on travel time than trail miles.
The statistical model for the route travel time is
$$T=\beta_{S}S+\beta_{R}R+\beta_{Z}Z+\varepsilon$$,
where $T$ is the travel time in hours, $S$ is the trail distance , $R$ is the cross country distance, $Z$ is the cumulative elevation of the route, $\varepsilon$ is the prediction error and sum of all uncertain effects, and $\beta_{S},\beta_{R},\beta_{Z}$ are the unknown regression coefficients.
$$S=\sum_{i=1}^{n}d\left(\boldsymbol{x}_{i},\boldsymbol{x}_{i-1}\right)\left(1-C_{i}\right)$$
$$R=\sum_{i=1}^{n}d\left(\boldsymbol{x}_{i},\boldsymbol{x}_{i-1}\right)C_{i}$$
$$Z=\sum_{i=1}^{n}\max\left(0,z_{i}-z_{i-1}\right)$$.
A multiple linear least squares regression gives us estimates of the coefficients $\beta_{S},\beta_{R},\beta_{Z}$. They appear in the ouput of the next cell with the labels miles_trail, miles_CC and elevation_gain_feet.
# multiple linear least squares regression of the backpacking data
import pandas as pd
from pandas.core import datetools
import statsmodels.api as sm
df_X = pd.DataFrame({'miles_trail': getMilesTrail(), 'miles_CC':getMilesCC(),
'elevation_gain_feet': getElevGainFeet()})
df_Y = pd.DataFrame({'total_hours': getTotalHours()})
model = sm.OLS(df_Y, df_X).fit()
model.params
$\hat{\beta}_{S} = 0.379191$
$\hat{\beta}_{R} = 1.334724$
$\hat{\beta}_{Z} = 0.001156$
$\hat{\beta}_{S}$ is equivalent to about 2.6 miles per hour. Cross-country travel is much slower on average than on trail, equivalent to about 0.75 miles per hour. The slower speed reflects the time consumed in route finding and in negotiating rough terrain. $\hat{\beta}_{Z}$ is equivalent to 1 hour for every 865 feet of climbing.
This model has a high level of unexplained variance due to other hidden factors. Cross-country travel, in particular, has a high degree of uncertainty. This section will estimate the prediction error of this model.
# root mean squared error of residuals
np.sqrt(model.mse_resid)
# plot the residuals versus total hours
fig = plt.figure(1, figsize=(8,8))
ax0 = fig.add_subplot(211)
ax0.plot(df_Y['total_hours'], np.abs(model.resid), linestyle='None', marker=u'D', color='dodgerblue');
ax0.grid()
ax0.set_xlabel('Total hours');
ax0.set_ylabel('Model prediction error (hours)');
# plot residual as percentage of total hours
ax1 = fig.add_subplot(212)
pctError = np.divide(np.abs(model.resid), df_Y['total_hours'])*100
ax1.plot(df_Y['total_hours'], pctError, linestyle='None', marker=u'D', color='dodgerblue');
ax1.grid()
ax1.set_xlabel('Total hours');
ax1.set_ylabel('Model prediction error (%)');
plt.show()
# calculate percentage error for just those observations greater than 2 hours
isGt2 = np.greater(df_Y['total_hours'], 2.0)
print('Average prediction error (%):', np.mean(pctError[isGt2]))
The model error grows with the length of the hike. A reasonably good margin for uncertainty is 17 to 34 percent of the total estimated travel time.
This is the backpacking data in tabulated form.
df = pd.DataFrame({'trip': getDataColumn(0), 'route_segment': getDataColumn(1), 'miles_trail': getMilesTrail(),
'miles_CC':getMilesCC(), 'elevation_gain_feet': getElevGainFeet(), 'total_hours': getTotalHours()})
df[['trip','route_segment','miles_trail','miles_CC','elevation_gain_feet','total_hours']]