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.
chanshing committed Dec 10, 2024

Unverified

This user has not yet uploaded their public signing key.
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
@@ -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"),
@@ -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']
@@ -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']
@@ -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
):
"""
@@ -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:
@@ -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')

0 comments on commit f178a49

Please sign in to comment.