/*_________________________________________________________________________________________ Series: SAS Code for Accelerometer Data Cleaning and Management Name: Step 1 - Input Date: July 30, 2015 For: Healthy Start / Départ Santé Purpose: Read text-formated, raw data into SAS and stack respondents into 1 data file called "paxraw". Input: .AWC files (Accelerometer data files) Output: Paxraw (Raw accelerometer data) 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) (http://mathieubelanger.recherche.usherbrooke.ca/index.htm). Version: 1.3 Citation: Boudreau, J. & Bélanger, M. SAS Code for Accelerometer Data Cleaning and Management: Step 1 - Input, Version 1.3. http://mathieubelanger.recherche.usherbrooke.ca/Actical.htm#2-5. Updated July 30, 2015. Accessed [date accessed: month day, year]. _________________________________________________________________________________________*/ /****************** STEP 1.1 SPECIFY A WORK DIRECTORY ************************************************/ /* Choose directories where the input files are located, where the outputs will be saved, and where the reports will be stored. */ *----------------------------------------------------------------------------------------------------*; %LET pi_path1 = C:\Users\Example\SAS Data; /* Input directory */ %LET po_path1 = C:\Users\Example\SAS Output; /* Output directory */ %LET po_path2 = &po_path1.\Reports; /* Report directory */ /****************** STEP 1.2 TO SET FILE IDENTIFICATION LENGTH *************************************/ /* Specify the length of the input file names (without the .AWC file extension), the ID numbers, and the accelerometer serial identification numbers. */ *----------------------------------------------------------------------------------------------------*; %LET id_length = 4; /* Length of ID number */ %LET file_length = 4; /* file name (without .AWC extension) */ %LET serial_length = 7; /* Length of accelerometer serial number */ /****************** STEP 1.3 DEFINE THE EPOCH LENGTH *************************************************/ /* Define the desired epoch length in seconds. Valid options are 1, 15, 30 and 60 seconds. If the accelerometer data is recorded at intervals shorter than the specified length, the observations will be averaged into coarser units (through binning). If the data is recorded in longer epochs, it cannot be refined into smaller intervals. The default value is set to 15 second epochs. */ *----------------------------------------------------------------------------------------------------*; %LET epoch_length = 15; /* NOTE: 1 sec epoch times are coded as am_epoch = 0. This value may need to be modified so that it corresponds to the accelerometer output. */ /****************** STEP 1.4 EXECUTION ****************************************************************/ /* From this point onwards, no modifications are required for Code Step 1. */ *----------------------------------------------------------------------------------------------------*; *----------------------------------------------------------------------------------------------------*; * Assign library, and file names. *; *----------------------------------------------------------------------------------------------------*; /* Inputs */ FILENAME rawdata "&pi_path1\*.awc"; /* Outputs */ LIBNAME outlib "&po_path1"; LIBNAME reports "&po_path2"; *----------------------------------------------------------------------------------------------------*; * Reformat the month date values (*.awc). *; *----------------------------------------------------------------------------------------------------*; /* NOTE: The accelerometers used in this study were programmed using the local system time. Date-time conflicts occured when the regional language was not english. The following is used to convert french date strings to english. This format is not necessary to run the code, and may be modified or omitted. */ PROC FORMAT; INVALUE $AFDate(UPCASE default=3) 'JAN', 'JANV' = 'JAN' 'FEV', 'FÉV', 'FEVR', 'FÉVR', 'FEB' = 'FEB' 'MAR', 'MARS' = 'MAR' 'APR', 'AVR', 'AVRL', 'AVRI', 'AVRIL' = 'APR' 'MAY', 'MAI' = 'MAY' 'JUN', 'JUNE', 'JUIN', 'JUI' = 'JUN' 'JUL', 'JULY', 'JUIL', 'JUILL' = 'JUL' 'AUG', 'AOU', 'AOUT', 'AOÛ', 'AOÛT' = 'AUG' 'SEP', 'SEPT' = 'SEP' 'OCT' = 'OCT' 'NOV' = 'NOV' 'DEC', 'DÉC' = 'DEC' other = _same_; RUN; *-----------------------------------------------------------------------------------------------------*; * Read and reformat the accelerometer data files (*.AWC). *; *-----------------------------------------------------------------------------------------------------*; DATA c reports._Invalid_Data_Characters(KEEP= seqn rec am_epoch err totalfiles paxinten paxstep) reports._Invalid_Inten_Step (KEEP= seqn rec am_epoch err totalfiles paxinten paxstep); RETAIN TotalFiles TotalObs err row grp delM 0 sdtv badStartDate seconds am_identify_no am_strt_date_raw am_strt_date am_strt_time am_serial_no am_epoch; ATTRIB grp LENGTH=8 FORMAT=comma14. ; LENGTH am_epoch 3 err row TotalObs paxinten paxstep seconds sdtv badStartDate am_strt_date am_strt_time 8 am_strt_date_raw $ 13 am_identify_no $ &id_length. seqn $ &file_length. am_serial_no $ &serial_length. rec $ 25 currfile $ 256 ; FORMAT date am_strt_date date9. am_strt_date_raw $13. am_strt_time tod8. paxinten TotalObs 8. paxstep 7.1 ; INFILE rawdata DELIMITER='$' TRUNCOVER FILENAME=currfile EOV=nf lrecl=50; INPUT @; IF (_N_ = 1 | nf = 1) THEN DO; INPUT am_identify_no $12. / am_strt_date_raw $13. / am_strt_time time9. / am_epoch 5. / / am_serial_no $12. / / / / / / rec $25. ; TotalFiles + 1; nf = 0; delM = 0; row = 1; TotalObs = _N_; IF AM_EPOCH in (0 1 2 4) THEN PUT "INTERESTING FILE: " CURRFILE " is a type " AM_EPOCH " epoch file."; am_strt_date = INPUT(CATS(SUBSTRN(COMPRESS(am_strt_date_raw,".","T"),1,FIND(am_strt_date_raw,"-","t",1)-1), INPUT(SUBSTRN(COMPRESS(am_strt_date_raw,".","T"),4,COUNTC(am_strt_date_raw,".","VT")-8),$AFDate.), SUBSTRN(COMPRESS(am_strt_date_raw,".","T"),FIND(COMPRESS(am_strt_date_raw,".","T"),"-","t", -COUNTC(am_strt_date_raw,".","VT"))+1,4)),date9.); badStartDate = .; sdtv = DHMS(am_strt_date, hour(am_strt_time), minute(am_strt_time), second(am_strt_time)); seconds = sdtv; /* Contains the SAS datetime value for any given record. */ /* If the monitor began recording at a time other than midnight, then tag that date as bad. Discard all records from that day, and begin keeping records at midnight on the following day. */ IF TIMEPART(sdtv) ne 0 THEN DO; badStartDate = DATEPART(sdtv); PUT _N_= badStartDate date8.; END; END; ELSE DO; row + 1; TotalObs = _N_; INPUT rec $; IF (am_epoch = 4) THEN seconds + 60; /* One record per minute. */ ELSE IF (am_epoch = 1) THEN seconds + 15; /* Four records per minute. */ ELSE IF (am_epoch = 2) THEN seconds + 30; /* Two records per minute. */ ELSE IF (am_epoch = 0) THEN seconds + 1; /* Sixty records per minute. */ END; newtime = PUT(seconds, datetime20.); date = DATEPART(seconds); /* Sequential day of recording should be only 1 thru 7. The read-in procedure is set to read data for 7 days starting at the first occurrence of midnight. */ IF DATEPART(seconds) = badStartDate THEN DO; row = 0; delM + 1; DELETE; END; seqn = SCAN(currfile, -2, '\.'); *---------------------------------------------------------------------------------------------------*; * Stop if an input filename does not contain the specified number of digits or does not have a *; * '.awc' file extension. *; * *; * NOTE: The filename statement used to specify the input files specifies files with the filename *; * format '*.awc'. Unfortunately, this specification allows files with extensions longer than 3 *; * characters, but that begin with the letters 'awc' to pass this test. Therefore we have to include *; * a check to ensure the extension is an exact match to 'awc' only. As an example, '*.awcm' is *; * invalid and should fail. Note, the specification on the filename statement, i.e. '*.awc' will *; * ensure filenames such as '*.tAWC' will not be read. *; * *; * NOTE: line 2 below is saying that filename lengths not equal to &id_length will not be read *; * properly. This was set up at the beginning of the program in Code Step 1.2 Change the "8" at *; * the beginning to the length of your data file names. *; *---------------------------------------------------------------------------------------------------*; CALL SYMPUT('ValidFName', ' '); IF (((LENGTH(SCAN(currfile, -2, '\.'))) NE &file_length.) | (NOT(NOTDIGIT(currfile, -3))) | (UPCASE(((SCAN(currfile, -1, '\.')))) NE "AWC")) THEN DO; CALL SYMPUT('dsname', "'c'"); CALL SYMPUT('message_detail', CATS("All input filenames should be 4 'digits' long and have the format '*.AWC'. This process attempted to read the following invalid filename,", CATS(PUT(currfile, 256.)))); CALL SYMPUT('ValidFName', 'NO'); STOP; END; *---------------------------------------------------------------------------------------------------*; * Validate am_epoch and remove excess data records. This code corrects epochs with shorter lengths *; * to the epoch length specified in Code Step 1.3. It does not refine larger epoch lengths to the *; * specified value. *; *---------------------------------------------------------------------------------------------------*; /* Code for 1 second epochs. */ IF (&epoch_length. = 1) THEN DO; IF (am_epoch = 4) THEN DO; IF (row > 10080) THEN DELETE; grp + 60; END; ELSE IF (am_epoch = 1) THEN DO; IF (row = 40320) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 40320 RECORDS... " SEQN= ; IF (ROW > 40320) THEN DELETE; grp + 15; END; ELSE IF (am_epoch = 2) THEN DO; IF (row = 20160) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 20160 RECORDS... " SEQN= ; IF (row > 20160) THEN DELETE; grp + 30; END; ELSE IF (am_epoch = 0) THEN DO; IF (row = 604800) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 40320 RECORDS... " SEQN= ; IF (row > 604800) THEN DELETE; grp + 1; END; ELSE PUT "PROBLEM: AM_EPOCH is out-of-range. Valid values are 0, 1, 2 and 4. " SEQN= AM_EPOCH= ; END; /* Code for 15 second epochs. */ ELSE IF (&epoch_length. = 15) THEN DO; IF (am_epoch = 4) THEN DO; IF (row > 10080) THEN DELETE; grp + 4; END; ELSE IF (am_epoch = 1) THEN DO; IF (row = 40320) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 40320 RECORDS... " SEQN= ; IF (row > 40320) THEN DELETE; grp + 1; END; ELSE IF (am_epoch = 2) THEN DO; IF (row = 20160) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 20160 RECORDS... " SEQN= ; IF (row > 20160) THEN DELETE; grp + 2; END; ELSE IF (am_epoch = 0) THEN DO; IF (row = 604800) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 40320 RECORDS... " SEQN= ; IF (row > 604800) THEN DELETE; IF (MOD(row, 15) = 1) THEN grp + 1; END; ELSE PUT "PROBLEM: AM_EPOCH is out-of-range. Valid values are 0, 1, 2 and 4. " SEQN= AM_EPOCH= ; END; /* Code for 30 second epochs. */ ELSE IF (&epoch_length. = 30) THEN DO; IF (am_epoch = 4) THEN DO; IF (row > 10080) THEN DELETE; grp + 2; END; ELSE IF (am_epoch = 1) THEN DO; IF (row = 40320) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 40320 RECORDS... " SEQN= ; IF (row > 40320) THEN DELETE; IF (MOD(row, 2) = 1) THEN grp + 1; END; ELSE IF (am_epoch = 2) THEN DO; IF (row = 20160) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 20160 RECORDS... " SEQN= ; IF (row > 20160) THEN DELETE; grp + 1; END; ELSE IF (am_epoch = 0) THEN DO; IF (row = 604800) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 40320 RECORDS... " SEQN= ; IF (row > 604800) THEN DELETE; IF (MOD(row, 30) = 1) THEN grp + 1; END; ELSE PUT "PROBLEM: AM_EPOCH is out-of-range. Valid values are 0, 1, 2 and 4. " SEQN= AM_EPOCH= ; END; /* Code for 60 second epochs. */ ELSE IF (&epoch_length. = 60) THEN DO; IF (am_epoch = 4) THEN DO; IF (row > 10080) THEN DELETE; grp + 1; END; ELSE IF (am_epoch = 1) THEN DO; IF (ROW = 40320) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 40320 RECORDS... " SEQN= ; IF (row > 40320) THEN DELETE; IF (MOD(row, 4) = 1) THEN grp + 1; END; ELSE IF (am_epoch = 2) THEN DO; IF (ROW = 20160) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 20160 RECORDS... " SEQN= ; IF (row > 20160) THEN DELETE; IF (MOD(row, 2) = 1) THEN grp + 1; END; ELSE IF (am_epoch = 0) THEN DO; IF (ROW = 604800) THEN PUT /// "INTERESTING FILE: THIS EPOCH " AM_EPOCH "FILE HAS AT LEAST 40320 RECORDS... " SEQN= ; IF (row > 604800) THEN DELETE; IF (MOD(row, 60) = 1) THEN grp + 1; END; ELSE PUT "PROBLEM: AM_EPOCH is out-of-range. Valid values are 0, 1, 2 and 4. " SEQN= AM_EPOCH= ; END; /* Code for invalid epoch lengths. */ ELSE put "Epoch lenght is not 1, 15, 30, or 60 seconds."; *---------------------------------------------------------------------------------------------------*; * Clean the paxinten/paxstep records, (data lines 12+), by removing ANY letters of the alphabet or *; * double quotes. Note, in particular, the letter 'M' is present many times. *; * *; * NOTE: Occurances of double quotes or the letter 'M' do not warrant halting the process. Instead *; * they are removed and the code attempts to read valid values for paxinten and paxstep. *; *---------------------------------------------------------------------------------------------------*; IF (INDEXC(rec, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '"')) THEN DO; err + 1; OUTPUT reports._Invalid_Data_Characters; END; r2 = COMPBL(TRANSLATE(rec, ' ', '",M')); *----------------------------------------------------------------------------------------------------*; * Attempt to read values for both paxinten and paxstep.; *----------------------------------------------------------------------------------------------------*; LENGTH x y $ 12; x = SCAN(r2, 1, ' '); y = SCAN(r2, 2, ' '); /* Expected data format. */ IF (NOT(INDEXC(x, '.')) & INDEXC(y, '.')) THEN DO; /* 1 , 1.2 */ paxinten = INPUT(x, 5. ); paxstep = INPUT(y, 6.1); END; /* Occassionally observed senario. */ ELSE IF (NOT(INDEXC(x, '.')) & MISSING(y) ) THEN DO; /* 1 , */ paxinten = INPUT(x, 5.); paxstep = 0; OUTPUT reports._Invalid_Inten_Step; END; /* Custom data format, not expected to be encountered. */ ELSE IF ( INDEXC(x, '.') & MISSING(y) ) THEN DO; /* 1.1 , */ paxinten = 0; paxstep = INPUT(x, 6.1); OUTPUT reports._Invalid_Inten_Step; END; /* Format found in converted .AWCF files. */ ELSE IF (NOT(INDEXC(x, '.')) & NOT(INDEXC(y, '.'))) THEN DO; /* 1 , 1 */ paxinten = INPUT(x, 5.); paxstep = INPUT(y, 6.1); OUTPUT reports._Invalid_Inten_Step; END; /* No other patterns are expected. */ ELSE DO; PUT "CAUTION: " x= y= paxinten= paxstep= /; OUTPUT reports._Invalid_Inten_Step; END; OUTPUT c; RUN; *-------------------------------------------------------------------------------------------------------*; * A note is written in the log for any files that don't have a 'full' set of records. *; * *; * NOTE: This section only notifies the user via a message in the log that a file does not contain a *; * full set of observations. It is not an essential part of the code and may be deleted if undesired. *; *-------------------------------------------------------------------------------------------------------*; PROC SORT DATA=c OUT=tempc; BY seqn row; RUN; DATA _NULL_; SET tempc; BY seqn; IF last.seqn THEN DO; IF (((am_epoch = 4) & (row NE 10080)) | ((am_epoch = 2) & (row NE 20160)) | ((am_epoch = 1) & (row NE 40320))) THEN PUT / "CAUTION: file " seqn " is an epoch " am_epoch " file with too few rows " row comma14. " ; (4)10,080 (2)20,160 (1)40,320."; END; RUN; *---------------------------------------------------------------------------------------------------*; * Summation for am_epoch types with lenghts shorter than the value specified in Code Step 1.3, *; * where paxinten or paxstep are not missing. *; *---------------------------------------------------------------------------------------------------*; PROC SQL; CREATE TABLE summed AS SELECT *, SUM(paxinten) FORMAT=comma8.1 AS temp_paxinten, SUM(paxstep) FORMAT=comma8.1 AS temp_paxstep FROM c GROUP BY grp ORDER BY totalobs, grp; QUIT; *-------------------------------------------------------------------------------------------------------*; * Keep epoch length observations only and derive new variables. *; *-------------------------------------------------------------------------------------------------------*; DATA derives; SET summed; BY grp; RETAIN minutes sec; LENGTH paxn day 6 problem $ 15 paxday $ 9 minutes sec paxn 8 paxhour paxminut paxsec day 4; FORMAT paxn 7. day 3.; /* Code for 1 second epochs. */ IF &epoch_length. = 1 THEN DO; IF ((am_epoch = 0)); END; /* Code for 15 second epochs. */ ELSE IF &epoch_length. = 15 THEN DO; IF ((am_epoch = 1) | (am_epoch = 0 & first.grp)); IF (am_epoch IN (0)) THEN DO; am_epoch = 4; paxinten = temp_paxinten; paxstep = temp_paxstep; END; END; /* Code for 30 second epochs. */ ELSE IF &epoch_length. = 30 THEN DO; IF ((am_epoch = 2) | (am_epoch = 1 & first.grp) | (am_epoch = 0 & first.grp)); IF (am_epoch IN (0 1)) THEN DO; am_epoch = 2; paxinten = temp_paxinten; paxstep = temp_paxstep; END; END; /* Code for 60 second epochs. */ ELSE IF &epoch_length. = 60 THEN DO; IF ((am_epoch = 4) | (am_epoch = 2 & first.grp) | (am_epoch = 1 & first.grp) | (am_epoch = 0 & first.grp)); IF (am_epoch in (0 1 2)) THEN DO; am_epoch = 4; paxinten = temp_paxinten; paxstep = temp_paxstep; END; END; /* Code for invalid epochs. */ ELSE PUT "PROBLEM: AM_EPOCH is out-of-range. Valid values are 0, 1, 2 and 4. " seqn= am_epoch= ; problem = SCAN(r2, 3, ' '); /* Define the observation days and times. */ paxsec = SECOND(TIMEPART(seconds)); paxminut = MINUTE(TIMEPART(seconds)); paxhour = HOUR(TIMEPART(seconds)); paxday = PUT(DATEPART(seconds), downame.); /* Day of week - name (e.g Sunday), */ /* Remember, the value of minute in row 1 is 0 only when recording begins at midnight, i.e. only the first minute after midnight is minute 0 of the day. */ IF row = 1 THEN DO; paxn = 0; /* Initialize sequential observation number, PAXN. */ minutes = (paxhour * 60) + paxminut - 1; /* Initialize the minute of observation. */ sec = ((paxhour * 60) + paxminut - 1) * 60 + paxsec; /* Initialize the second of observation. */ END; paxn + 1; /* Increase the sequential observation number. */ sec + &epoch_length.; /* Compute the second of the day by incrementing the previous value by the epoch length defined in Code Step 1.3. */ IF (MOD(sec,60) = 0) THEN minutes + 1; /* Compute the minute of the day using the modulus of the seconds. */ day = floor(((sec + (60-&epoch_length.)) / 86400) + 1); /* Sequential day of recording. */ RUN; /* Write a short note to the log and save counts for more detailed report later. */ DATA REPORT(KEEP= TotalFiles TotalObs); SET derives END=last; IF last THEN DO; TotalObs = _N_; PUT // "______________________________________________________________________________________________________"; PUT // "NOTE: " TotalFiles " input files (*.AWC) were read from which " _N_ comma14. " records were kept." ; PUT // "______________________________________________________________________________________________________"; OUTPUT report; END; RUN; *---------------------------------------------------------------------------------------------------*; * Produce a report indicating the number of observations read from each file. *; *---------------------------------------------------------------------------------------------------*; OPTIONS NOCENTER NODATE FORMCHAR="|----|+|---+=|-/\<>*"; PROC FREQ DATA=derives NOPRINT; TABLES seqn / OUT=o(DROP= PERCENT); RUN; DATA _NULL_; CALL SYMPUT('t', CAT(PUT(DATEPART(DATETIME()), weekdate30.) , ' ', PUT(DATETIME(), timeampm11.))); RUN; PROC TEMPLATE; DEFINE STYLE Styles.Custom; PARENT = styles.fancyPrinter; REPLACE fonts / 'TitleFont' = ("Palatino, Arial, Times Roman", 13pt, Bold Italic) 'TitleFont2' = ("Palatino, Arial, Times Roman", 13pt, Bold Italic) 'StrongFont' = ("Palatino, Book Antiqua, Times Roman", 10pt, Bold) 'EmphasisFont' = ("Palatino, Book Antiqua, Times Roman", 12pt, Bold Italic) 'FixedEmphasisFont' = ("Courier", 10pt, Italic) 'FixedStrongFont' = ("Courier", 10pt, Bold) 'FixedHeadingFont' = ("Courier", 10pt, Bold) 'BatchFixedFont' = ("Arial", 11pt) 'FixedFont' = ("Palatino", 10pt) 'headingEmphasisFont' = ("ITC Zaph Chancery, Palatino, Times Roman", 13pt, Bold Italic) 'headingFont' = ("ITC Zaph Chancery, Palatino, Times Roman", 13pt, Italic) 'docFont' = ("Palatino, Times Roman", 10pt); REPLACE Table from Output / frame = box rules = rows cellpadding = 4pt cellspacing = 0.1pt borderwidth = 0.1pt background = color_list('bgT'); REPLACE color_list / 'link' = blue 'bgH' = cxccffcc 'bgT' = white 'bgD' = white 'fg' = black 'bg' = white; END; RUN; PROC FORMAT; VALUE cfore low -< 10080 = 'white' 10080 = 'black' 10080 <-< 20160 = 'white' 20160 = 'black' 20160 <-< 40320 = 'white' 40320 = 'black' 40320 <-< 604800 = 'white' 604800 = 'white' 604800 <- high = 'white' other = 'white'; VALUE cback low -< 10080 = 'red' 10080 = 'white' 10080 <-< 20160 = 'green' 20160 = 'yellow' 20160 <-< 40320 = 'orange' 40320 = 'yellow' 40320 <-< 604800 = 'green' 604800 = 'yellow' 604800 <- high = 'blue' other = 'red'; RUN; ODS LISTING CLOSE; ODS PDF STARTPAGE=no NOTOC FILE="&po_path2\_AM_Packer_Report.pdf" STYLE=custom; DATA x; SET report o; FILE PRINT; TITLE1 "Healthy Start/Départ Santé Step 1 Process Report "; FOOTNOTE1 "Healthy Start/Départ Santé data processed using Canadian Health Measures Survey methods "; IF (_N_ = 1) THEN PUT /// "Total files read: " @81 TotalFiles 11. // "Total records written to file ""Paxraw_Site"": " @60 TotalObs comma11. ; RUN; PROC REPORT DATA=o NOWD HEADLINE HEADSKIP; COLUMN seqn count; RBREAK AFTER / DOL SUMMARIZE; DEFINE seqn / WIDTH=25 FORMAT=$25. 'File (*.AWC)' STYLE=[cellwidth=45mm]; DEFINE count / ANALYSIS SUM WIDTH=18 FORMAT=comma10.0 'Observations' STYLE=[cellwidth=35mm] STYLE(column) = {background=cback. foreground=cfore.}; RUN; *---------------------------------------------------------------------------------------------------*; * Cleanup *; *---------------------------------------------------------------------------------------------------*; ODS PDF CLOSE; ODS LISTING; FILENAME rawdata CLEAR; *---------------------------------------------------------------------------------------------------*; * Sort and output a permanant copy to disk (in SAS format). *; *---------------------------------------------------------------------------------------------------*; PROC SORT DATA=derives(KEEP=am_identify_no am_strt_date date am_strt_time am_epoch am_serial_no seqn paxn paxinten paxstep day paxhour paxminut paxsec paxday) OUT= outlib.paxraw; BY seqn paxn; RUN; *-----------------------------------------------------------------------------------------------*; * THIS CODE ENDS WITH A DATAFILE CALLED "PAXRAW" TO BE READ INTO CODE STEP 2. *; *-----------------------------------------------------------------------------------------------*;