/*_________________________________________________________________________________________ Series: SAS Code for Accelerometer Data Cleaning and Management Name: Step 2 - Quality Control Date: July 30, 2015 For: Healthy Start / Départ Santé Purpose: Apply quality control measures to the data, restrict the analysis to certain days of the week, determine subsamples based on time intervals, and compute wear and non-wear times. Input: Paxraw (from Step 1) (Raw accelerometer data) Output: Weartime (Wear time data for each day) Perminute (Epoch count and step data with non-wear dummy variables) Info: This series of codes is based on the scripts prepared by Statistics Canada for the analysis of data from the Health Measures Survey, the Actical Accelerometer Data Analysis Support Tool (Accel+), which is available from the CHEO Research Institute for Healthy Active Living and Obesity Research (HALO) (http://www.haloresearch.ca/accel/). The current version of this code was prepared by Jonathan Boudreau and Mathieu Bélanger at the Centre de Formation Médicale du Nouveau-Brunswick (CFMNB). Version: 1.3 Citation: Boudreau, J. & Bélanger, M. SAS Code for Accelerometer Data Cleaning and Management: Step 2 - Quality Control, Version 1.3. http://mathieubelanger.recherche.usherbrooke.ca/Actical.htm#2-5. Updated July 30, 2015. Accessed [date accessed: month day, year]. _________________________________________________________________________________________*/ /****************** STEP 2.1 SPECIFY A WORK DIRECTORY ***********************************************/ * Make sure you are using the same one as in Code Step 1. *-------------------------------------------------------------------------------------------------------*; %LET po_path1 = C:\Users\Example\SAS Output; /****************** STEP 2.2 SPECIFY A LOCATION FOR THE EXCEL FILE OUTPUT ****************************/ * Specify an Excel file to output your summary dataset. *----------------------------------------------------------------------------------------------------*; %LET xls_path = &po_path1.\WearTimes.xls; /****************** STEP 2.3 WEAR TIME INTERRUPTION PERIOD *******************************************/ /* By default, the wear inturruption period is set to 60 minutes. */ *-------------------------------------------------------------------------------------------------------*; %LET nw_period = 60; /****************** STEP 2.4 EPOCH UNIT CONVERSION **********************************************/ /* Define the desired epoch length in seconds. Valid options are 1, 15, 30 and 60 seconds. The constant for unit conversion from epoch lengths into minutes is computed automatically. The epoch lengths must be the same as in Step 1. The default value is set for 15 second epochs. */ *----------------------------------------------------------------------------------------------------*; %LET epoch_length = 15; %LET conv_epoch = %EVAL(60 / &epoch_length.); /* Ex: 1 min = 4 * 15 sec */ /****************** STEP 2.5 DATA SUBSET *************************************************************/ /* Determine the days of the week on which to perform the analysis. Additionally, the procedure may be restriced to a specific time interval. Values are computed for entire dataset, the time interval (denoted by DC), and the interval's compliment (denoted by NDC), for the specified days only. */ /* The days of the week are coded as: 1 = Sunday 2 = Monday 3 = Tuesday 4 = Wednesday 5 = Thursday 6 = Friday 7 = Saturday NOTE: The default value (2, 3, 4, 5, 6) restricts the analysis to weekdays only. */ /* The hours and minutes defining the boundaries of the time interval may be set to integer values ranging from respectively 0 to 23 and 0 to 59. NOTE: The starting time is inclusive, whereas the ending time is exclusive. */ *------------------------------------------------------------------------------------------------------*; %LET Days = 2, 3, 4, 5, 6; /* Days to include in the analysis. */ %LET DC_Time_Strt_hr = 7; /* Starting hour for the time interval of interest. */ %LET DC_Time_Strt_min = 30; /* Starting minute for the time interval of interest (inclusive). */ %LET DC_Time_End_hr = 17; /* Ending hour for the time interval of interest. */ %LET DC_Time_End_min = 30; /* Ending minute for the time interval of interest (exclusive). */ /****************** STEP 2.6 QUALITY CONTROL THRESHOLDS *********************************************/ /* Set the levels at which activity counts and number of steps per minute are considered to be spurious (erronous). Thresholds are scaled by the number of epochs per minute and evaluated at the epoch level. It is recommended that default values remain unchanged, however they may be increased if epoch lengths are small, since the spurious criteria becomes more strict as epoch lengths decrease. */ *----------------------------------------------------------------------------------------------------*; %LET spur_cpm = 20000*2; /* Spurious count threshold (default = 20000 per minute) */ %LET spur_step = 253; /* Spurious step threshold (default = 253 steps per minute) */ %LET max_spur_cnt = 15; /* Maximum number of tolerable spurious counts (default = 15) */ %LET max_step_cnt = 15; /* Maximum number of tolerable spurious steps (default = 15) */ %LET print_spur = 1; /* Print spurious observations (yes = 1, no = 0) (default = 1) */ /****************** STEP 2.7 MISCALIBRATION DETECTION ************************************************/ /* Determine if the accelerometer data should be verified for miscalibrations. Set the threshold used to detect a miscalibration and the action to perform in the event a miscalibration is detected. A miscalibration is encountered when the base acceleration count value is not 0. When this occurs, a large percent of the observed counts are registered as the same non-zero value. It is unknown how non-zero base values affect the observed counts. Since testing may be a lengthy process, it is skipped by default (percent_skip = 1). In the event that a miscalibration is detected, the default action is to halt the process and display the ID values of the affected entries (percent_action = 0). Depending on the data, the threshold percent of the total epochs per day may need to be adjusted. By default a miscalibration is detected when a single non-zero count value is observed in 40% or more of the data (percent_level = 0.4). */ *----------------------------------------------------------------------------------------------------*; %LET percent_skip = 1; /* Skip testing for accelerometer miscalibration (Yes = 1, No = 0) */ %LET percent_level = 0.4; /* Threshold for percent of total data (0 to 1) (default = 0.4) */ %LET percent_action = 0; /* Action when threshold is attained (Display = 0, Delete = 1) */ /****************** STEP 2.8 EXECUTION ****************************************************************/ /* From this point onwards, no modifications are required for Step 2. */ /* Optionally, observations may be excluded from the analysis by adding the clinicid value to the BadID data set. */ *--------------------------------------------------------------------------------------------------------*; /* General purpose macros */ /* Observation count macro */ %MACRO CountObs(ds); %LET DSID=%SYSFUNC(OPEN(&ds.,IN)); %LET CountObs=%SYSFUNC(ATTRN(&DSID,NOBS)); %LET RC=%SYSFUNC(CLOSE(&DSID)); &CountObs %MEND; /* Outputs */ LIBNAME outlib "&po_path1"; *------------------------------------------------------------------------------------------------------*; * Remove certain IDs from the data set (optional). *; *------------------------------------------------------------------------------------------------------*; DATA BadID; INFILE DATALINES; INPUT clinicid $6.; /* IDs to remove from the analysis */ DATALINES; ; RUN; PROC SQL NOPRINT; SELECT '"'||TRIM(LEFT(clinicid))||'"' INTO :BadID SEPARATED BY ' ,' FROM BadID; QUIT; *------------------------------------------------------------------------------------------------------*; * Read in pawraw file from Code Step 1. *; *------------------------------------------------------------------------------------------------------*; /* Create a clean file called "am1" without extra variables. */ DATA am1; SET outlib.paxraw(RENAME=(seqn=clinicid paxinten=amcount paxstep=amstep day=dayworn paxsec=sec paxhour=hour paxminut=minute paxday=dayofweek_worn paxn=obsn)); KEEP am_identify_no clinicid amcount amstep dayworn hour minute sec dayofweek_worn obsn; RUN; DATA am1; SET am1; IF clinicid IN(&BadID.) THEN DELETE; RUN; PROC SORT DATA=am1; BY clinicid dayworn obsn; RUN; /* Determine which days of the week to keep in the dataset based on the values specified in Step 2.5. */ DATA WeekDayNames; FORMAT DayNum 8. DayName DOWNAME.; DO i = 1 TO 7; DayNum = i; DayName = WEEKDAY(i + 2); OUTPUT; END; DROP i; RUN; PROC SQL NOPRINT; SELECT "'"||TRIM(LEFT(PUT(DayName,DOWNAME.)))||"'" INTO :DaySample SEPARATED BY ', ' FROM WeekDayNames WHERE DayNum IN (&Days); QUIT; /* Identify observations that occur between the times specified in Step 2.5. */ DATA am1; SET am1; FORMAT DCHOURS 4.; LENGTH DCHOURS 4; SELECT (left(dayofweek_worn)); WHEN (&DaySample) DO; SELECT; WHEN (hour < &DC_Time_Strt_hr.) DCHOURS = 0; WHEN (hour = &DC_Time_Strt_hr.) DO; IF minute >= &DC_Time_Strt_min. THEN DCHOURS = 1; ELSE DCHOURS = 0; END; WHEN (hour > &DC_Time_Strt_hr. AND hour < &DC_Time_End_hr.) DCHOURS = 1; WHEN (hour = &DC_Time_End_hr.) DO; IF minute < &DC_Time_End_min. THEN DCHOURS = 1; ELSE DCHOURS = 0; END; WHEN (hour > &DC_Time_End_hr.) DCHOURS = 0; END; END; OTHERWISE DCHOURS = 0; END; RUN; %LET am_labels = clinicid = "ID" amcount = "Count" amstep = "Steps" obsn = "Observation" dayofweek_worn = "Day of week" dayworn = "Day" hour = "Hour" minute = "Minute" sec = "Second"; /* Check for spurious counts using the value from Step 2.6. */ DATA am_spurious_counts; SET am1; count20000=0; IF amcount GE &spur_cpm./&conv_epoch. THEN count20000=1; LABEL &am_labels.; RUN; /* Check for spurious steps using the value from Step 2.6. */ DATA am_spurious_steps; SET am1; step253=0; IF amstep GE &spur_step./&conv_epoch. THEN step253=1; LABEL &am_labels.; RUN; /* Produce a summary report indicating how many occurrences of spurious data there are. If there are 15 (&max_spur_cnt. and &max_step_cnt.) or more occurrences, then the raw data should be investigated further, and an error is output to the log. If there are less than 15 (&max_spur_cnt. and &max_step_cnt.) occurrences, the spurious data correction code should be run. */ PROC MEANS DATA = am_spurious_counts NOPRINT; VAR count20000; OUTPUT OUT = spur_counts SUM=SpuriousCounts; RUN; DATA _NULL_; SET spur_counts; CALL SYMPUTX("SpuriousCounts",SpuriousCounts,'G'); RUN; PROC MEANS DATA = am_spurious_steps NOPRINT; VAR step253; OUTPUT OUT = spur_steps SUM=SpuriousSteps; RUN; DATA _NULL_; SET spur_steps; CALL SYMPUTX("SpuriousSteps",SpuriousSteps,'G'); RUN; *-----------------------------------------------------------------------------------------------------*; * Clean the data for spurious counts and steps. *; * NOTE: This section is not necessary if reports above showed no spurious data. *; *-----------------------------------------------------------------------------------------------------*; /* Quality Control - Spurious Counts */ %MACRO Spurious_Counts; OPTION NONOTES NOMPRINT; /* Idenfity records with intensity counts >= 20000 (or &spur_cpm) */ DATA invalid_am; SET am1; invalid_cnt = 0; IF amcount >= &spur_cpm./&conv_epoch. THEN invalid_cnt=invalid_cnt+1; RUN; /* Create new values for records with intensity count >= 20000 (or &spur_cpm). */ DATA replace_am(KEEP= am_identify_no clinicid amcount amstep dayworn hour minute sec dayofweek_worn obsn); SET invalid_am; BY clinicid dayworn obsn; /* obsn_invalid = first minute with an intensity count >= &spur_cpm. */ /* obsn_valid = first minute with a valid intensity count (< &spur_cpm). */ /* last_int = last minute with a valid intensity count. */ RETAIN obsn_invalid obsn_valid last_int; /* Save the epoch with intensity count => &spur_cpm. */ IF amcount >= &spur_cpm./&conv_epoch. AND obsn_invalid=. AND NOT last.dayworn THEN obsn_invalid=obsn; /* When one or more epochs with intensity counts => &spur_cpm, take the average of the valid intensity counts immediately before and after the invalid epochs. */ ELSE IF amcount > . AND amcount < &spur_cpm./&conv_epoch. THEN DO; IF obsn_invalid NE . THEN DO; sv_int=amcount; sv_obsn=obsn; /* One or more consecutive epochs with count >= &spur_cpm. */ DO i=obsn_valid+1 TO obsn-1; obsn=i; amcount=ROUND(sum(sv_int,last_int)/2); OUTPUT; END; obsn_invalid=.; /* Last epoch with a valid intensity. */ last_int=sv_int; obsn_valid=sv_obsn; END; ELSE DO; obsn_valid=obsn; last_int=amcount; END; END; /* If the last epoch of the day has intensity count >= &spur_cpm, use the last valid epoch. */ ELSE IF amcount >= &spur_cpm./&conv_epoch. AND last.dayworn THEN DO; amcount=last_int; IF obsn_invalid NE . THEN DO; DO i=obsn_invalid TO obsn; /* >=1 consecutive epochs of count >= 20000 up to the last epoch. */ obsn=i; output; END; END; ELSE OUTPUT; END; RUN; /* Sort the file with invalid data to be replaced. */ PROC SORT DATA=replace_am; BY clinicid dayworn obsn; RUN; /* Update am1 and write a note to the log. */ %IF %CountObs(replace_am) > 0 %THEN %DO; DATA am1; UPDATE am1 replace_am; BY clinicid dayworn obsn; RUN; %IF %CountObs(replace_am) EQ &SpuriousCounts. %THEN %DO; OPTION NOTES; %PUT NOTE: %CountObs(replace_am) spurious counts were corrected.; OPTION NONOTES; %END; %ELSE %DO; %PUT WARNING: The dataset contains %SYSFUNC(&SpuriousCounts. - %CountObs(replace_am)) spurious counts.; %END; %END; %ELSE %DO; %PUT WARNING: No values were replaced.; %END; PROC DATASETS NOPRINT; DELETE invalid_am replace_am; RUN; OPTION NOTES; %MEND Spurious_Counts; %MACRO Check_Spurious_Counts; %IF &SpuriousCounts. LE 0 %THEN %DO; %PUT NOTE: No spurious counts detected.; %END; %ELSE %DO; %IF &SpuriousCounts. LE &max_spur_cnt. %THEN %DO; %PUT NOTE: &SpuriousCounts. spurious counts detected, applying quality control measures.; %Spurious_Counts; %END; %ELSE %DO; %IF &print_spur. EQ 1 %THEN %DO; PROC PRINT DATA=am_spurious_counts LABEL NOOBS; VAR clinicid obsn amcount amstep dayofweek_worn dayworn hour minute sec; WHERE count20000=1; RUN; %END; %PUT ERROR: &SpuriousCounts. spurious counts detected, the data should be examined.; %ABORT CANCEL; %END; %END; %MEND Check_Spurious_Counts; %Check_Spurious_Counts; /* Quality Control - Spurious Steps */ %MACRO Spurious_Steps; DATA invalid_step; SET am1; invalid_stp = 0; IF amstep >= &spur_step./&conv_epoch. THEN invalid_stp=invalid_stp+1; RUN; /* Create new values for records with step >= &spur_step. */ DATA replace_step (keep= am_identify_no clinicid amcount amstep dayworn hour minute sec dayofweek_worn obsn); SET invalid_step; BY clinicid dayworn obsn; /* obsn_invalid = first epoch with an intensity step >= &spur_step. */ /* obsn_valid = first epoch with a valid intensity step (< &spur_step.). */ /* last_int = last epoch with a valid intensity step. */ RETAIN obsn_invalid obsn_valid last_int; /* Save the first epoch with intensity step => &spur_step. */ IF amstep >= &spur_step./&conv_epoch. AND obsn_invalid=. AND NOT last.dayworn THEN obsn_invalid=obsn; /* When one or more epochs with intensity steps => &spur_step., take the average of the valid intensity steps immediately before and after the invalid epochs. */ ELSE IF amstep > . AND amstep < (&spur_step./&conv_epoch.) THEN DO; IF obsn_invalid NE . THEN DO; sv_int=amstep; sv_obsn=obsn; /* One or more consecutive epochs with step >= &spur_step. */ DO i=obsn_valid+1 TO obsn-1; obsn=i; amstep=ROUND(sum(sv_int,last_int)/2); OUTPUT; END; obsn_invalid=.; /* Last epoch with a valid intensity. */ last_int=sv_int; obsn_valid=sv_obsn; END; ELSE DO; obsn_valid=obsn; last_int=amstep; END; END; /* If the last epoch of the day has intensity step >= &spur_step., use the last valid epoch. */ ELSE IF amstep >= (&spur_step./&conv_epoch.) AND last.dayworn THEN DO; amstep=last_int; IF obsn_invalid NE . THEN DO; DO i=obsn_invalid TO obsn; /* >=1 consecutive epochs of step >= &spur_step. up to the last epoch. */ obsn=i; OUTPUT; END; END; ELSE OUTPUT; END; RUN; /* Sort the file with invalid data to be replaced */ PROC SORT DATA=replace_step; BY clinicid dayworn obsn; RUN; /* Update am1 and output a note to the log. */ %IF %CountObs(replace_step) > 0 %THEN %DO; DATA am1; UPDATE am1 replace_step; BY clinicid dayworn obsn; RUN; %IF %CountObs(replace_step) EQ &SpuriousSteps. %THEN %DO; OPTION NOTES; %PUT NOTE: %CountObs(replace_step) spurious steps were corrected.; OPTION NONOTES; %END; %ELSE %DO; %PUT WARNING: The dataset contains %SYSFUNC(&SpuriousSteps. - %CountObs(replace_step)) spurious steps.; %END; %END; %ELSE %DO; %PUT WARNING: No values were replaced.; %END; PROC DATASETS NOPRINT; DELETE invalid_step replace_step; RUN; OPTION NOTES; %MEND Spurious_Steps; %MACRO Check_Spurious_Steps; %IF &SpuriousSteps. LE 0 %THEN %DO; %PUT NOTE: No spurious steps detected.; %END; %ELSE %DO; %IF &SpuriousSteps. LE &max_step_cnt. %THEN %DO; %PUT NOTE: &SpuriousSteps. spurious steps detected, applying quality control measures.; %Spurious_Steps; %END; %ELSE %DO; %IF &print_spur. EQ 1 %THEN %DO; PROC PRINT DATA=am_spurious_steps LABEL NOOBS; VAR clinicid obsn amcount amstep dayofweek_worn dayworn hour minute sec; WHERE step253=1; RUN; %END; %PUT ERROR: &SpuriousSteps. spurious steps detected, the data should be examined.; %ABORT CANCEL; %END; %END; %MEND Check_Spurious_Steps; %Check_Spurious_Steps; *-------------------------------------------------------------------------------------------------*; * Detect miscalibrations. *; *-------------------------------------------------------------------------------------------------*; %MACRO Detect_Miscalibrations; %IF &percent_skip. NE 1 %THEN %DO; PROC SQL NOPRINT; CREATE TABLE amcounts AS SELECT DISTINCT a.clinicid, a.dayworn, a.amcount, (count(a.amcount) / (SELECT count(b.amcount) FROM am1 b WHERE b.clinicid EQ a.clinicid AND b.dayworn EQ a.dayworn)) AS percent FROM am1 a GROUP BY clinicid, dayworn, amcount ORDER BY clinicid, dayworn, amcount; CREATE TABLE FlagID AS SELECT DISTINCT clinicid, (CASE WHEN (SELECT max(b.percent) FROM amcounts b WHERE b.clinicid EQ a.clinicid AND b.amcount >0) >= &percent_level. THEN 1 ELSE 0 END) AS Flg FROM amcounts a ORDER BY clinicid; CREATE TABLE DropID AS SELECT clinicid FROM FlagID WHERE Flg = 1 ORDER BY clinicid; QUIT; %LOCAL Miscalibrated; %IF %CountObs(DropID) GT 0 %THEN %DO; %PUT WARNING: %CountObs(DropID) accelerometers appear to be improperly calibrated!; %IF &percent_action. EQ 1 %THEN %DO; PROC SQL NOPRINT; SELECT '"'||clinicid||'"' INTO :Miscalibrated SEPARATED BY ', ' FROM DropID; QUIT; DATA am1; SET am1(WHERE=(clinicid NOT IN (&Miscalibrated.))); RUN; %PUT NOTE: %CountObs(DropID) individuals were removed from the data set.; %END; %ELSE %DO; PROC PRINT DATA=DropID; RUN; %PUT ERROR: %CountObs(DropID) accelerometers appear to be miscalibrated, the data should be examined.; %ABORT CANCEL; %END; %END; %ELSE %DO; %PUT NOTE: No miscalibrations detected!; %END; %END; %ELSE %DO; %PUT NOTE: Testing for accelerometer miscalibration was skipped!; %END; %MEND Detect_Miscalibrations; %Detect_Miscalibrations; *-------------------------------------------------------------------------------------------------*; * Determine the wear time per day. *; *-------------------------------------------------------------------------------------------------*; %macro nw(nwperiod=,convfct=); DATA nonwear; SET am1; BY clinicid dayworn obsn; IF first.dayworn THEN nw_num=0; /* Non-wear period number */ IF first.dayworn OR reset OR stopped THEN DO; strt_nw=0; /* Starting epoch for the non-wear period */ end_nw=0; /* Ending epoch for the non-wear period */ start=0; /* Indicator for starting to count the non-wear period */ dur_nw=0; /* Duration of the non-wear period*/ reset=0; /* Indicator for resetting and starting over */ stopped=0; /* Indicator for stopping the non-wear period */ cnt_non_zero=0; /* Counter for the number of epochs with intensity between 1 and 100 */ END; RETAIN nw_num strt_nw end_nw stopped reset start cnt_non_zero dur_nw; /* The non-wear period starts with a zero count. */ IF amcount=0 AND start=0 THEN DO; strt_nw=obsn; /* Assign the starting epoch of non-wear. */ start=1; END; /* Accumulate the number of the non-wear epochs. */ /* Keep track of the ending epoch for the non-wear period. */ IF start and amcount=0 THEN end_nw=obsn; /* Keep track of the number of epochs with intensity between 1-100. */ IF 0100 is encountered. */ IF (cnt_non_zero=3*&convfct OR amcount=. OR amcount>100/&convfct ) THEN DO; IF dur_nw<&nwperiod*&convfct THEN reset=1; /* Reset if less than &nwperiod minutes of non-wear. */ ELSE stopped=1; END; /* Last epoch of the day. */ IF last.dayworn AND dur_nw>=&nwperiod*&convfct THEN stopped=1; /* Output one record for each non-wear period. */ IF stopped=1 THEN DO; nw_num=nw_num+1; KEEP clinicid dayworn nw_num strt_nw end_nw dur_nw; OUTPUT; END; RUN; /* Create a dataset with one record per epoch, for the non-wear periods only*/ DATA nw_minutes(KEEP=clinicid dayworn obsn); SET nonwear; BY clinicid dayworn nw_num; DO i=strt_nw TO end_nw BY 1; obsn=i; OUTPUT; END; RUN; DATA am1; MERGE am1(IN=in_all) nw_minutes(IN=inw); BY clinicid dayworn obsn; IF in_all; nonwear = inw; RUN; %MEND nw; %MACRO nw_summary(dataset=,nwperiod=,convfct=); *-----------------------------------------------------------------------------*; * Summarize the total nonwear time for everyone in the analysis. *; *-----------------------------------------------------------------------------*; PROC SUMMARY DATA=&dataset; BY clinicid dayworn; VAR nonwear; OUTPUT OUT=sum_nw SUM=tot_dur_nw_brut; RUN; *-----------------------------------------------------------------------------*; * Summarize the total number of valid epochs for everyone in the analysis. *; *-----------------------------------------------------------------------------*; PROC SUMMARY DATA=&dataset; BY clinicid dayworn; VAR amcount; OUTPUT OUT=sum_all N=tot_min_brut; RUN; *-----------------------------------------------------------------------*; * Define hours of wear (in epoch lengths). *; *-----------------------------------------------------------------------*; /* Summarize the wear epochs. */ PROC SUMMARY DATA=&dataset; BY clinicid dayworn; VAR amcount; WHERE nonwear=0; OUTPUT OUT=sum_wear SUM=tot_cnt_wr N=tot_min_wr_brut; RUN; *---------------------------------------------------------------------------------*; * Final data for one record per day for everyone in the analysis (in hours). *; *---------------------------------------------------------------------------------*; DATA nw&nwperiod; MERGE sum_all(IN=in_all) sum_nw(IN=in_nw) sum_wear; BY clinicid dayworn; IF in_all; IF tot_min_brut=. THEN tot_min=.; ELSE tot_min=tot_min_brut / &convfct; IF tot_dur_nw_brut=. THEN tot_dur_nw=.; ELSE tot_dur_nw=tot_dur_nw_brut / &convfct; IF tot_min_wr_brut=. THEN tot_min_wr=.; ELSE tot_min_wr=tot_min_wr_brut / &convfct; IF tot_dur_nw=. THEN tot_dur_nw=0; IF tot_min_wr=. THEN tot_min_wr=0; IF tot_cnt_wr=. THEN tot_cnt_wr=0; wear_hr=tot_min_wr/60; tot_dur_nw=tot_dur_nw/60; LABEL tot_dur_nw='Number of non-wear hours in a day' wear_hr='Number of wear hours in a day' tot_min='Number of valid minutes in a day' tot_cnt_wr='Total intensity counts from wear time in a day' tot_min_wr='Number of wear minutes in a day' ; KEEP clinicid dayworn tot_min tot_min_wr wear_hr tot_cnt_wr tot_dur_nw; RUN; %MEND nw_summary; PROC SORT DATA = am1; BY clinicid dayworn obsn; RUN; %nw(nwperiod=&nw_period.,convfct=&conv_epoch.); /* Runs the nw macro using the value defined in Code Step 2.3. */ *-------------------------------------------------------------------------------------------------*; * Determine the wear time per day. *; *-------------------------------------------------------------------------------------------------*; %nw_summary(dataset=am1,nwperiod=&nw_period.,convfct=&conv_epoch.); /* Runs the nonwear macro using the value defined in Code Step 2.3. */ DATA wearall; SET nw&nw_period.; KEEP clinicid dayworn tot_cnt_wr tot_min tot_dur_nw tot_min_wr wear_hr; RUN; PROC SORT DATA=wearall; BY clinicid dayworn; RUN; *-------------------------------------------------------------------------------------------------*; * Determine the wear time per day during subset hours. *; *-------------------------------------------------------------------------------------------------*; %nw_summary(dataset=am1(WHERE=(DCHOURS=1)),nwperiod=&nw_period.,convfct=&conv_epoch.); /* Runs the nonwear macro using the value defined in Code Step 2.3. */ DATA weardc; SET nw&nw_period.(RENAME=(tot_cnt_wr=tot_cnt_wr_dc tot_min=tot_min_dc tot_dur_nw=tot_dur_nw_dc tot_min_wr=tot_min_wr_dc wear_hr=wear_hr_dc)); KEEP clinicid dayworn tot_cnt_wr_dc tot_min_dc tot_dur_nw_dc tot_min_wr_dc wear_hr_dc; RUN; PROC SORT DATA=weardc; BY clinicid dayworn; RUN; *-------------------------------------------------------------------------------------------------*; * Determine the wear time per day during non-subset hours. *; *-------------------------------------------------------------------------------------------------*; %nw_summary(dataset=am1(WHERE=(DCHOURS=0)),nwperiod=&nw_period.,convfct=&conv_epoch.); /* Runs the nonwear macro using the value defined in Code Step 2.3. */ DATA wearndc; SET nw&nw_period.(RENAME=(tot_cnt_wr=tot_cnt_wr_ndc tot_min=tot_min_ndc tot_dur_nw=tot_dur_nw_ndc tot_min_wr=tot_min_wr_ndc wear_hr=wear_hr_ndc)); KEEP clinicid dayworn tot_cnt_wr_ndc tot_min_ndc tot_dur_nw_ndc tot_min_wr_ndc wear_hr_ndc; RUN; PROC SORT DATA=wearndc; BY clinicid dayworn; RUN; *-------------------------------------------------------------------------------------------------*; * Merge and output wear times per day. *; *-------------------------------------------------------------------------------------------------*; DATA Wear; MERGE wearall(IN=iall) weardc(IN=idc) wearndc; BY clinicid dayworn; IF iall AND idc; RUN; DATA outlib.weartime; SET Wear; RUN; PROC EXPORT DATA = Wear OUTFILE = "&xls_path." REPLACE; RUN; *-------------------------------------------------------------------------------------------------*; * Create and output a data set that only includes valid days. *; *-------------------------------------------------------------------------------------------------*; PROC SORT DATA = am1; BY clinicid dayworn obsn; RUN; DATA wear1; SET wearall; KEEP clinicid dayworn; RUN; DATA valid; MERGE am1 (IN=d) wear1 (IN=e); BY clinicid dayworn; IF e; RUN; DATA outlib.perminute; SET valid; RUN; *-----------------------------------------------------------------------------------------------------*; * THIS CODE ENDS WITH TWO DATAFILES CALLED "WEARTIME " AND"PERMINUTE" TO BE READ INTO CODE STEP 3. *; *-----------------------------------------------------------------------------------------------------*;