166 lines
6.2 KiB
Python
166 lines
6.2 KiB
Python
import numpy as np
|
|
import pandas as pd
|
|
|
|
import features.esm
|
|
|
|
QUESTIONNAIRE_ID_SAM = {
|
|
"event_stress": 87,
|
|
"event_threat": 88,
|
|
"event_challenge": 89,
|
|
"event_time": 90,
|
|
"event_duration": 91,
|
|
"event_work_related": 92,
|
|
"period_stress": 93,
|
|
}
|
|
QUESTIONNAIRE_ID_SAM_LOW = min(QUESTIONNAIRE_ID_SAM.values())
|
|
QUESTIONNAIRE_ID_SAM_HIGH = max(QUESTIONNAIRE_ID_SAM.values())
|
|
|
|
GROUP_QUESTIONNAIRES_BY = [
|
|
"participant_id",
|
|
"device_id",
|
|
"esm_session",
|
|
]
|
|
# Each questionnaire occurs only once within each esm_session on the same device within the same participant.
|
|
|
|
|
|
def extract_stressful_events(df_esm: pd.DataFrame) -> pd.DataFrame:
|
|
# 0. Select only questions from Stress Appraisal Measure.
|
|
df_esm_preprocessed = features.esm.preprocess_esm(df_esm)
|
|
df_esm_sam = df_esm_preprocessed[
|
|
(df_esm_preprocessed["questionnaire_id"] >= QUESTIONNAIRE_ID_SAM_LOW)
|
|
& (df_esm_preprocessed["questionnaire_id"] <= QUESTIONNAIRE_ID_SAM_HIGH)
|
|
]
|
|
|
|
df_esm_sam_clean = features.esm.clean_up_esm(df_esm_sam)
|
|
# 1.
|
|
df_esm_event_threat_challenge_mean_wide = calculate_threat_challenge_means(
|
|
df_esm_sam_clean
|
|
)
|
|
# 2.
|
|
df_esm_event_stress = detect_stressful_event(df_esm_sam_clean)
|
|
|
|
# Join to the previously calculated features related to the events.
|
|
df_esm_events = df_esm_event_threat_challenge_mean_wide.join(
|
|
df_esm_event_stress[
|
|
GROUP_QUESTIONNAIRES_BY + ["event_present", "event_stressfulness"]
|
|
].set_index(GROUP_QUESTIONNAIRES_BY)
|
|
)
|
|
|
|
# 3.
|
|
df_esm_event_work_related = detect_event_work_related(df_esm_sam_clean)
|
|
|
|
df_esm_events = df_esm_events.join(
|
|
df_esm_event_work_related[
|
|
GROUP_QUESTIONNAIRES_BY + ["event_work_related"]
|
|
].set_index(GROUP_QUESTIONNAIRES_BY)
|
|
)
|
|
|
|
# 4.
|
|
df_esm_event_time = convert_event_time(df_esm_sam_clean)
|
|
|
|
df_esm_events = df_esm_events.join(
|
|
df_esm_event_time[GROUP_QUESTIONNAIRES_BY + ["event_time"]].set_index(
|
|
GROUP_QUESTIONNAIRES_BY
|
|
)
|
|
)
|
|
|
|
# 5.
|
|
df_esm_event_duration = extract_event_duration(df_esm_sam_clean)
|
|
|
|
df_esm_events = df_esm_events.join(
|
|
df_esm_event_duration[
|
|
GROUP_QUESTIONNAIRES_BY + ["event_duration", "event_duration_info"]
|
|
].set_index(GROUP_QUESTIONNAIRES_BY)
|
|
)
|
|
|
|
return df_esm_events
|
|
|
|
|
|
def calculate_threat_challenge_means(df_esm_sam_clean: pd.DataFrame) -> pd.DataFrame:
|
|
# Select only threat and challenge assessments for events
|
|
df_esm_event_threat_challenge = df_esm_sam_clean[
|
|
(
|
|
df_esm_sam_clean["questionnaire_id"]
|
|
== QUESTIONNAIRE_ID_SAM.get("event_threat")
|
|
)
|
|
| (
|
|
df_esm_sam_clean["questionnaire_id"]
|
|
== QUESTIONNAIRE_ID_SAM.get("event_challenge")
|
|
)
|
|
]
|
|
# Calculate mean of threat and challenge subscales for each ESM session.
|
|
df_esm_event_threat_challenge_mean_wide = pd.pivot_table(df_esm_event_threat_challenge, index=["participant_id","device_id", "esm_session"], columns=["questionnaire_id"], values=["esm_user_answer_numeric"], aggfunc="mean")
|
|
# Drop unnecessary column values.
|
|
df_esm_event_threat_challenge_mean_wide.columns = df_esm_event_threat_challenge_mean_wide.columns.get_level_values(1)
|
|
df_esm_event_threat_challenge_mean_wide.columns.name = None
|
|
df_esm_event_threat_challenge_mean_wide.rename(columns={
|
|
QUESTIONNAIRE_ID_SAM.get("event_threat"): "threat_mean",
|
|
QUESTIONNAIRE_ID_SAM.get("event_challenge"): "challenge_mean"
|
|
}, inplace=True)
|
|
return df_esm_event_threat_challenge_mean_wide
|
|
|
|
|
|
def detect_stressful_event(df_esm_sam_clean: pd.DataFrame) -> pd.DataFrame:
|
|
df_esm_event_stress = df_esm_sam_clean[
|
|
df_esm_sam_clean["questionnaire_id"] == QUESTIONNAIRE_ID_SAM.get("event_stress")
|
|
]
|
|
df_esm_event_stress = df_esm_event_stress.assign(
|
|
event_present=lambda x: x.esm_user_answer_numeric > 0,
|
|
event_stressfulness=lambda x: x.esm_user_answer_numeric,
|
|
)
|
|
return df_esm_event_stress
|
|
|
|
|
|
def detect_event_work_related(df_esm_sam_clean: pd.DataFrame) -> pd.DataFrame:
|
|
df_esm_event_stress = df_esm_sam_clean[
|
|
df_esm_sam_clean["questionnaire_id"]
|
|
== QUESTIONNAIRE_ID_SAM.get("event_work_related")
|
|
]
|
|
df_esm_event_stress = df_esm_event_stress.assign(
|
|
event_work_related=lambda x: x.esm_user_answer_numeric > 0
|
|
)
|
|
return df_esm_event_stress
|
|
|
|
|
|
def convert_event_time(df_esm_sam_clean: pd.DataFrame) -> pd.DataFrame:
|
|
df_esm_event_time = df_esm_sam_clean[
|
|
df_esm_sam_clean["questionnaire_id"] == QUESTIONNAIRE_ID_SAM.get("event_time")
|
|
].assign(
|
|
event_time=lambda x: pd.to_datetime(
|
|
x.esm_user_answer, errors="coerce", infer_datetime_format=True, exact=True
|
|
)
|
|
)
|
|
return df_esm_event_time
|
|
|
|
|
|
def extract_event_duration(df_esm_sam_clean: pd.DataFrame) -> pd.DataFrame:
|
|
df_esm_event_duration = df_esm_sam_clean[
|
|
df_esm_sam_clean["questionnaire_id"]
|
|
== QUESTIONNAIRE_ID_SAM.get("event_duration")
|
|
].assign(
|
|
event_duration=lambda x: pd.to_datetime(
|
|
x.esm_user_answer.str.slice(start=0, stop=-6), errors="coerce"
|
|
).dt.time
|
|
)
|
|
# TODO Explore the values recorded in event_duration and possibly fix mistakes.
|
|
# For example, participants reported setting 23:50:00 instead of 00:50:00.
|
|
|
|
# For the events that no duration was found (i.e. event_duration = NaT),
|
|
# we can determine whether:
|
|
# - this event is still going on ("1 - It is still going on")
|
|
# - the participant couldn't remember it's duration ("0 - I do not remember")
|
|
# Generally, these answers were converted to esm_user_answer_numeric in clean_up_esm,
|
|
# but only the numeric types of questions and answers.
|
|
# Since this was of "datetime" type, convert these specific answers here again.
|
|
df_esm_event_duration["event_duration_info"] = np.nan
|
|
df_esm_event_duration[
|
|
df_esm_event_duration.event_duration.isna()
|
|
] = df_esm_event_duration[df_esm_event_duration.event_duration.isna()].assign(
|
|
event_duration_info=lambda x: x.esm_user_answer.str.slice(stop=1).astype(int)
|
|
)
|
|
|
|
return df_esm_event_duration
|
|
|
|
|
|
# TODO: How many questions about the stressfulness of the period were asked and how does this relate to events?
|