|
11 | 11 | import numpy as np
|
12 | 12 | import pandas as pd
|
13 | 13 | import joblib
|
| 14 | +import matplotlib.pyplot as plt |
| 15 | +import matplotlib.dates as mdates |
14 | 16 | from numba import njit
|
15 | 17 |
|
16 | 18 | from stepcount import utils
|
@@ -388,6 +390,10 @@ def main():
|
388 | 390 | print(daily_adj.set_index('Date').drop(columns='Filename'))
|
389 | 391 | print("\nOutput files saved in:", outdir)
|
390 | 392 |
|
| 393 | + print("\nPlotting...") |
| 394 | + fig = plot(Y, title=basename) |
| 395 | + fig.savefig(f"{outdir}/{basename}-Steps.png", bbox_inches='tight', pad_inches=0) |
| 396 | + |
391 | 397 | after = time.time()
|
392 | 398 | print(f"Done! ({round(after - before,2)}s)")
|
393 | 399 |
|
@@ -1085,6 +1091,73 @@ def numba_detect_bouts(
|
1085 | 1091 | return bouts
|
1086 | 1092 |
|
1087 | 1093 |
|
| 1094 | +def plot(Y, title=None): |
| 1095 | + """ |
| 1096 | + Plot time series of steps per minute for each day. |
| 1097 | +
|
| 1098 | + Parameters: |
| 1099 | + - Y: pandas Series or DataFrame with a 'Steps' column. Must have a DatetimeIndex. |
| 1100 | +
|
| 1101 | + Returns: |
| 1102 | + - fig: matplotlib figure object |
| 1103 | + """ |
| 1104 | + |
| 1105 | + MAX_STEPS_PER_MINUTE = 180 |
| 1106 | + |
| 1107 | + if isinstance(Y, pd.DataFrame): |
| 1108 | + Y = Y['Steps'] |
| 1109 | + |
| 1110 | + assert isinstance(Y, pd.Series), "Y must be a pandas Series, or a DataFrame with a 'Steps' column" |
| 1111 | + |
| 1112 | + # Resample to 1 minute intervals |
| 1113 | + # Note: .sum() returns 0 when all values are NaN, so we need to use a custom function |
| 1114 | + def _sum(x): |
| 1115 | + if x.isna().all(): |
| 1116 | + return np.nan |
| 1117 | + return x.sum() |
| 1118 | + |
| 1119 | + Y = Y.resample('1T').agg(_sum) |
| 1120 | + |
| 1121 | + dates_index = Y.index.normalize() |
| 1122 | + unique_dates = dates_index.unique() |
| 1123 | + |
| 1124 | + # Set the plot figure and size |
| 1125 | + fig = plt.figure(figsize=(10, len(unique_dates) * 2)) |
| 1126 | + |
| 1127 | + # Group by each day |
| 1128 | + for i, (day, y) in enumerate(Y.groupby(dates_index)): |
| 1129 | + ax = fig.add_subplot(len(unique_dates), 1, i + 1) |
| 1130 | + |
| 1131 | + # Plot steps |
| 1132 | + ax.plot(y.index, y, label='steps/min') |
| 1133 | + |
| 1134 | + # Grey shading where NA |
| 1135 | + ax.fill_between(y.index, 0, MAX_STEPS_PER_MINUTE, where=y.isna(), color='grey', alpha=0.3, interpolate=True, label='missing') |
| 1136 | + |
| 1137 | + # Formatting the x-axis to show hours and minutes |
| 1138 | + ax.xaxis.set_major_locator(mdates.HourLocator(interval=1)) |
| 1139 | + ax.xaxis.set_minor_locator(mdates.MinuteLocator(interval=15)) |
| 1140 | + ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M")) |
| 1141 | + |
| 1142 | + # Set x-axis limits to start at 00:00 and end at 24:00 |
| 1143 | + ax.set_xlim(day, day + pd.DateOffset(days=1)) |
| 1144 | + # Set y-axis limits |
| 1145 | + ax.set_ylim(-10, MAX_STEPS_PER_MINUTE) |
| 1146 | + |
| 1147 | + ax.tick_params(axis='x', rotation=45) |
| 1148 | + ax.set_ylabel('steps/min') |
| 1149 | + ax.set_title(day.strftime('%Y-%m-%d')) |
| 1150 | + ax.grid(True) |
| 1151 | + ax.legend(loc='upper left') |
| 1152 | + |
| 1153 | + if title: |
| 1154 | + fig.suptitle(title) |
| 1155 | + |
| 1156 | + fig.tight_layout() |
| 1157 | + |
| 1158 | + return fig |
| 1159 | + |
| 1160 | + |
1088 | 1161 |
|
1089 | 1162 | if __name__ == '__main__':
|
1090 | 1163 | main()
|
0 commit comments