Skip to content

Commit

Permalink
feat: add command-line arguments for minimum walking time per day for…
Browse files Browse the repository at this point in the history
… cadence calculation

Add new command-line arguments `--peak1-min-walk-per-day`, `--peak30-min-walk-per-day`, and `--p95-min-walk-per-day` to specify the minimum amount of walking time per day required for calculating cadence peak1, peak30, and p95 respectively in the cadence summary.
  • Loading branch information
chanshing committed Dec 10, 2024
1 parent aadbfc6 commit f178a49
Showing 1 changed file with 40 additions and 14 deletions.
54 changes: 40 additions & 14 deletions src/stepcount/stepcount.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ def main():
parser.add_argument("--exclude-first-last", "-e",
help="Exclude first, last or both days of data. Default: None (no exclusion)",
type=str, choices=['first', 'last', 'both'], default=None)
parser.add_argument("--peak1-min-walk-per-day",
help="The minimum amount of walking time per day required for peak1 calculation.",
type=int, default=10)
parser.add_argument("--peak30-min-walk-per-day",
help="The minimum amount of walking time per day for peak30 calculation.",
type=int, default=30)
parser.add_argument("--p95-min-walk-per-day",
help="The minimum amount of walking time per day for p95 calculation.",
type=int, default=10)
parser.add_argument("--start",
help=("Specicfy a start time for the data to be processed (otherwise, process all). "
"Pass values as strings, e.g.: '2024-01-01 10:00:00'. Default: None"),
Expand Down Expand Up @@ -263,7 +272,12 @@ def main():
info.update({f'WalkingAdjusted(mins)_Hour{h:02}_Weekday': steps_summary_adj['weekday_hour_walks'].loc[h] for h in range(24)})

# Cadence summary
cadence_summary = summarize_cadence(Y, model.steptol)
cadence_summary = summarize_cadence(
Y, model.steptol,
peak1_min_walk_per_day=args.peak1_min_walk_per_day,
peak30_min_walk_per_day=args.peak30_min_walk_per_day,
p95_min_walk_per_day=args.p95_min_walk_per_day
)
# overall stats
info['CadencePeak1(steps/min)'] = cadence_summary['cadence_peak1']
info['CadencePeak30(steps/min)'] = cadence_summary['cadence_peak30']
Expand All @@ -278,7 +292,13 @@ def main():
info['Cadence95th(steps/min)_Weekday'] = cadence_summary['weekday_cadence_p95']

# Cadence summary, adjusted
cadence_summary_adj = summarize_cadence(Y, model.steptol, adjust_estimates=True)
cadence_summary_adj = summarize_cadence(
Y, model.steptol,
peak1_min_walk_per_day=args.peak1_min_walk_per_day,
peak30_min_walk_per_day=args.peak30_min_walk_per_day,
p95_min_walk_per_day=args.p95_min_walk_per_day,
adjust_estimates=True
)
info['CadencePeak1Adjusted(steps/min)'] = cadence_summary_adj['cadence_peak1']
info['CadencePeak30Adjusted(steps/min)'] = cadence_summary_adj['cadence_peak30']
info['Cadence95thAdjusted(steps/min)'] = cadence_summary_adj['cadence_p95']
Expand Down Expand Up @@ -810,6 +830,9 @@ def _tdelta_to_str(tdelta):
def summarize_cadence(
Y: pd.Series,
steptol: int = 3,
peak1_min_walk_per_day: int = 10,
peak30_min_walk_per_day: int = 30,
p95_min_walk_per_day: int = 10,
adjust_estimates: bool = False
):
"""
Expand All @@ -818,6 +841,9 @@ def summarize_cadence(
Parameters:
- Y (pd.Series): A pandas Series of step counts.
- steptol (int, optional): The minimum number of steps per window for the window to be considered valid for calculation. Defaults to 3 steps per window.
- peak1_min_walk_per_day (int, optional): The minimum number of walking minutes per day for peak1 cadence calculation. Defaults to 10 minutes.
- peak30_min_walk_per_day (int, optional): The minimum number of walking minutes per day for peak30 cadence calculation. Defaults to 30 minutes.
- p95_min_walk_per_day (int, optional): The minimum number of walking minutes per day for 95th percentile cadence calculation. Defaults to 10 minutes.
- adjust_estimates (bool, optional): Whether to adjust estimates to account for missing data. Defaults to False.
Returns:
Expand All @@ -829,31 +855,31 @@ def summarize_cadence(

# TODO: split walking and running cadence?

def _cadence_max(x, steptol, walktol=30, n=1):
y = x[x >= steptol]
def _cadence_max(x, min_steps_per_min, min_walk_per_day=30, n=1):
y = x[x >= min_steps_per_min]
# if not enough walking time, return NA.
# note: walktol in minutes, x must be minutely
if len(y) < walktol:
# note: min_walk_per_day in minutes, x must be minutely
if len(y) < min_walk_per_day:
return np.nan
return y.nlargest(n, keep='all').mean()

def _cadence_p95(x, steptol, walktol=30):
y = x[x >= steptol]
def _cadence_p95(x, min_steps_per_min, min_walk_per_day=30):
y = x[x >= min_steps_per_min]
# if not enough walking time, return NA.
# note: walktol in minutes, x must be minutely
if len(y) < walktol:
# note: min_walk_per_day in minutes, x must be minutely
if len(y) < min_walk_per_day:
return np.nan
return y.quantile(.95)

dt = utils.infer_freq(Y.index).total_seconds()
steptol_in_minutes = steptol * 60 / dt # rescale steptol to steps/min
min_steps_per_min = steptol * 60 / dt # rescale steptol to steps/min
minutely = Y.resample('T').sum().rename('Steps') # steps/min

# cadence https://jamanetwork.com/journals/jama/fullarticle/2763292

daily_cadence_peak1 = minutely.resample('D').agg(_cadence_max, steptol=steptol_in_minutes, walktol=10, n=1).rename('CadencePeak1(steps/min)')
daily_cadence_peak30 = minutely.resample('D').agg(_cadence_max, steptol=steptol_in_minutes, walktol=30, n=30).rename('CadencePeak30(steps/min)')
daily_cadence_p95 = minutely.resample('D').agg(_cadence_p95, walktol=10, steptol=steptol_in_minutes).rename('Cadence95th(steps/min)')
daily_cadence_peak1 = minutely.resample('D').agg(_cadence_max, min_steps_per_min=min_steps_per_min, min_walk_per_day=peak1_min_walk_per_day, n=1).rename('CadencePeak1(steps/min)')
daily_cadence_peak30 = minutely.resample('D').agg(_cadence_max, min_steps_per_min=min_steps_per_min, min_walk_per_day=peak30_min_walk_per_day, n=30).rename('CadencePeak30(steps/min)')
daily_cadence_p95 = minutely.resample('D').agg(_cadence_p95, min_steps_per_min=min_steps_per_min, min_walk_per_day=p95_min_walk_per_day).rename('Cadence95th(steps/min)')

with warnings.catch_warnings():
warnings.filterwarnings('ignore', message='Mean of empty slice')
Expand Down

0 comments on commit f178a49

Please sign in to comment.