|
| 1 | + |
| 2 | +import os |
| 3 | +import pandas as pd |
| 4 | +import plotly.graph_objects as go |
| 5 | +import logging |
| 6 | +# Set up logging |
| 7 | +logging.basicConfig(level=logging.INFO) |
| 8 | +logger = logging.getLogger(__name__) |
| 9 | + |
| 10 | +class NewGroup: |
| 11 | + def __init__(self): |
| 12 | + self.obs_path = "/Shared/vosslabhpc/Projects/BOOST/ObservationalStudy/3-experiment/data/act-obs-test" |
| 13 | + self.int_path = "/Shared/vosslabhpc/Projects/BOOST/InterventionStudy/3-experiment/data/act-int-test" |
| 14 | + |
| 15 | + self.paths = [ |
| 16 | + os.path.join(self.obs_path, 'derivatives', 'GGIR-3.2.6-test-ncp-sleep'), |
| 17 | + os.path.join(self.int_path, 'derivatives', 'GGIR-3.2.6-test-ncp-sleep') |
| 18 | + ] |
| 19 | + |
| 20 | + self.path = './plots' |
| 21 | + |
| 22 | + |
| 23 | + def _parse_person_file(self, file_path): |
| 24 | + try: |
| 25 | + df = pd.read_csv(file_path) |
| 26 | + sleep = df["dur_spt_min_pla"].iloc[0] |
| 27 | + inactivity = df["dur_day_total_IN_min_pla"].iloc[0] |
| 28 | + light = df["dur_day_total_LIG_min_pla"].iloc[0] |
| 29 | + mod = df["dur_day_total_MOD_min_pla"].iloc[0] |
| 30 | + vig = df["dur_day_total_VIG_min_pla"].iloc[0] |
| 31 | + mvpa = mod + vig |
| 32 | + total = sleep + inactivity + light + mvpa |
| 33 | + logger.debug(f"Parsed {file_path}: sleep={sleep}, inactivity={inactivity}, light={light}, mvpa={mvpa}, total={total}") |
| 34 | + if total == 0: |
| 35 | + logger.warning(f"Total was 0 for {file_path}. Columns found: {df.columns.tolist()}") |
| 36 | + return None |
| 37 | + if df.empty: |
| 38 | + logger.warning(f"{file_path} is empty.") |
| 39 | + return None |
| 40 | + return { |
| 41 | + "Sleep": sleep / total * 1440, |
| 42 | + "Inactivity": inactivity / total * 1440, |
| 43 | + "Light": light / total * 1440, |
| 44 | + "MVPA": mvpa / total * 1440 |
| 45 | + } |
| 46 | + except Exception as e: |
| 47 | + logger.warning(f"Error reading file {file_path}: {e}") |
| 48 | + return None |
| 49 | + |
| 50 | + |
| 51 | + def plot_person(self): |
| 52 | + durations = [] |
| 53 | + |
| 54 | + for base_dir in self.paths: |
| 55 | + for entry in sorted(os.listdir(base_dir)): |
| 56 | + if not entry.startswith("sub-"): |
| 57 | + continue |
| 58 | + |
| 59 | + person_file = os.path.join( |
| 60 | + base_dir, entry, "accel", "output_accel", "results", |
| 61 | + "part5_personsummary_MM_L40M100V400_T5A5.csv" |
| 62 | + ) |
| 63 | + |
| 64 | + if not os.path.isfile(person_file): |
| 65 | + logger.debug(f"File not found for {entry}, skipping.") |
| 66 | + continue |
| 67 | + |
| 68 | + values = self._parse_person_file(person_file) |
| 69 | + if not values: |
| 70 | + continue |
| 71 | + |
| 72 | + values["Subject"] = entry |
| 73 | + durations.append(values) |
| 74 | + |
| 75 | + df_all = pd.DataFrame(durations) |
| 76 | + if not df_all.empty and "Subject" in df_all.columns: |
| 77 | + df_all = df_all[~df_all["Subject"].str.startswith("sub-6")].reset_index(drop=True) |
| 78 | + df_all = df_all.sort_values("Subject").reset_index(drop=True) |
| 79 | + else: |
| 80 | + logger.warning("No valid data found — skipping Subject filtering.") |
| 81 | + |
| 82 | + self._plot_stacked_bar(df_all, |
| 83 | + title="Normalized Average Activity Composition by Subject (All Sessions)", |
| 84 | + filename="avg_plot_all.html") |
| 85 | + def plot_session(self): |
| 86 | + durations = [] |
| 87 | + |
| 88 | + for base_dir in self.paths: |
| 89 | + for entry in sorted(os.listdir(base_dir)): |
| 90 | + if not entry.startswith("sub-"): |
| 91 | + continue |
| 92 | + |
| 93 | + subject_path = os.path.join(base_dir, entry, "accel") |
| 94 | + if not os.path.isdir(subject_path): |
| 95 | + continue |
| 96 | + |
| 97 | + for session_folder in sorted(os.listdir(subject_path)): |
| 98 | + if not session_folder.startswith("ses"): |
| 99 | + continue |
| 100 | + |
| 101 | + person_file = os.path.join( |
| 102 | + subject_path, |
| 103 | + session_folder, |
| 104 | + f"output_{session_folder}", |
| 105 | + "results", |
| 106 | + "part5_personsummary_MM_L40M100V400_T5A5.csv" |
| 107 | + ) |
| 108 | + |
| 109 | + if not os.path.isfile(person_file): |
| 110 | + logger.debug(f"Missing file for {entry}/{session_folder}, skipping.") |
| 111 | + continue |
| 112 | + |
| 113 | + values = self._parse_person_file(person_file) |
| 114 | + if not values: |
| 115 | + continue |
| 116 | + |
| 117 | + values["Subject"] = entry |
| 118 | + values["Session"] = session_folder |
| 119 | + durations.append(values) |
| 120 | + |
| 121 | + df_all = pd.DataFrame(durations) |
| 122 | + if not df_all.empty and "Subject" in df_all.columns and "Session" in df_all.columns: |
| 123 | + df_all = df_all[~df_all["Subject"].str.startswith("sub-6")].reset_index(drop=True) |
| 124 | + df_all = df_all.sort_values(["Session", "Subject"]).reset_index(drop=True) |
| 125 | + else: |
| 126 | + logger.warning("No valid data found — skipping Subject/Session filtering.") |
| 127 | + |
| 128 | + for session, group_df in df_all.groupby("Session"): |
| 129 | + self._plot_stacked_bar( |
| 130 | + group_df, |
| 131 | + title=f"Average Activity Composition — Session {session.upper()}", |
| 132 | + y_key="Subject", |
| 133 | + filename=f"avg_plot_{session}.html" |
| 134 | + ) |
| 135 | + def _plot_stacked_bar(self, df, title, filename, y_key="Subject"): |
| 136 | + if df.empty: |
| 137 | + logger.warning("No data to plot.") |
| 138 | + return |
| 139 | + |
| 140 | + activities = ["Sleep", "Inactivity", "Light", "MVPA"] |
| 141 | + colors = { |
| 142 | + "Sleep": "#A8DADC", |
| 143 | + "Inactivity": "#F1FAEE", |
| 144 | + "Light": "#FFD6A5", |
| 145 | + "MVPA": "#FF9F1C" |
| 146 | + } |
| 147 | + |
| 148 | + fig = go.Figure() |
| 149 | + for activity in activities: |
| 150 | + fig.add_trace(go.Bar( |
| 151 | + y=df[y_key], |
| 152 | + x=df[activity], |
| 153 | + name=activity, |
| 154 | + orientation='h', |
| 155 | + marker_color=colors[activity], |
| 156 | + hovertemplate=df[y_key] + f" - {activity} = " + |
| 157 | + (df[activity] / 60).round(2).astype(str) + " hr<extra></extra>" |
| 158 | + )) |
| 159 | + |
| 160 | + fig.update_layout( |
| 161 | + barmode='stack', |
| 162 | + title=title, |
| 163 | + xaxis=dict(title="Hours (normalized to 24)"), |
| 164 | + yaxis=dict(title=y_key), |
| 165 | + height=30 * len(df), |
| 166 | + margin=dict(l=20, r=20, t=40, b=20), |
| 167 | + showlegend=True, |
| 168 | + autosize=True |
| 169 | + ) |
| 170 | + os.makedirs(self.path, exist_ok=True) |
| 171 | + fig.write_html(os.path.join(self.path, filename)) |
0 commit comments