###### tags: `sas` `COMPUTE` `CALL DEFINE` `RTF control strings` `unicode` `first` `missing` `if` `countw` `%sysfunc` `%scan` `filename` `PROC IMPORT` `DEFINE` `PROC REPORT` # Creating tables using SAS base programming Chang's collection of working examples. Tables are styled in `SAS ODS RTF` and `PROC REPORT`. ## Table of contents :::info Add a table of contents for tables[^1]. ::: [^1]: SAS script file at `D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\scripts\master-file_supplementary-tables.sas` ![Table of contents](https://i.imgur.com/TS4kIta.png "Table of contents with hyper links to tables") --- ## Table title :::info Put the first sentence in bold typeface[^2] ::: [^2]: SAS script path D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch2_tabSup01_count-twin-pairs_binary-outcomes.sas ![Thesis TableS2-1](https://i.imgur.com/Lnz52dm.png "Bold only the first sentence of a long title") --- :::info Bold the first sentence and use superscript[^3]. ::: [^3]: SAS script path D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch3_tabSup03_fixed-effect-etimates-GSCAN-PRSs-on_illicit-drug-AU-CU-QIMR19Up.sas ![Thesis TableS3-3](https://i.imgur.com/6YMTrZg.png "Bold selective text with style + use unicode") --- ## Table header :::info Use two-layer spanned header[^4]. ::: [^4]: SAS script file at `D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch3_tabSup03_fixed-effect-etimates-GSCAN-PRSs-on_illicit-drug-AU-CU-QIMR19Up.sas` ![Thesis TableS3-1](https://i.imgur.com/xkaAYav.png "A spanned header with 2 layers") --- :::info Create three-layer spanned header[^5]. ::: [^5]: SAS script file at `D:\Now\library_genetics_epidemiology\slave_NU\NU_analytical_programs_tables\NU4tab06_MR-leave-one-out-analysis_results.sas` ![Table with 3 layer headers](https://i.imgur.com/zRW4565.png) --- :::info Use italic or superscript text as a shorthand[^6]. ::: [^6]: SAS script file at `D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch5_tabSup01_GWAS-sample-size-prevalence_LDSC-SNP-heritability-estimates.sas` ![Thesis TableS5-1](https://i.imgur.com/ZVBwKsS.png "h2 as a shorthand for hertiability estimate") --- ## Table body :::info Add colored horizontal lines to split groups[^7] ::: [^7]: SAS script file at D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch2_tabSup01_count-twin-pairs_binary-outcomes.sas ![Thesis TableS2-1](https://i.imgur.com/UPBahcG.png "Separate groups with cyan lines") --- :::info Add blank lines to split groups[^8]. ::: [^8]: SAS script file at `D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch2_tabSup02_1VarBin_basiAssum_difLL_significance.sas` ![Thesis TableS2-2](https://i.imgur.com/Q068k64.png "Separate groups with blank lines") --- :::info Add superscript letters to indicate levels of statistical significance[^9]. Superscript a indicates p value between 0.001 and 0.01. Superscript b indicates p value < 0.001. ::: [^9]: SAS script file at `D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch2_tabSup03_1VarCon_basiAssum_difLL_significance.sas` ![Thesis TableS2-3](https://i.imgur.com/z88EqC9.png "Add superscript letters to indicate levels of statistical significance") --- :::info Conditionally highlight selective columns based on values of a column[^10]. ::: [^10]: SAS script file at `D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch3_tabSup03_fixed-effect-etimates-GSCAN-PRSs-on_illicit-drug-AU-CU-QIMR19Up.sas` ![Thesis TableS3-3](https://i.imgur.com/qno1hBu.png "Mark Beta, SE, p value, and R^2^ in blue when p values are lower than a threshold") --- :::info Conditionally bold rows based on values of a column[^11]. ::: [^11]: SAS script files at `D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch5_tabSup02_two-sample-MR-results_non-cannabis-initiation_as_exposure_or_outcome.sas` `D:\Now\library_genetics_epidemiology\Chang_PhD_thesis\SAS-supp-table-scripts\Ch5_tabSup03_odds-ratio_exposure-UKB-CCPD-ESDPW-PYOS_outcome-ICC-CI_MR-sensitivity-analyses.sas` ![Thesis TableS5-2](https://i.imgur.com/E52xmJy.png "Put rows in bold-phase when p values are lower than a threshold") --- ![Thesis TableS5-3](https://i.imgur.com/tA2zNMk.png "Put rows in bold-phase when p values are lower than a threshold") --- :::info Keep rows with missing values and sort the data by a variable’s unformatted values[^12]. ::: [^12]: SAS script file at `D:\Now\library_genetics_epidemiology\slave_NU\NU_analytical_programs_tables\NU4tab06_MR-leave-one-out-analysis_results.sas` ![Table with missing values & sorted by a variable](https://i.imgur.com/MFYDnlY.png) --- ## Tables in Excel files :::info Export SAS tables to an Excel file[^13] ::: [^13]: **SAS script file path** D:\z_old_files\national_inpatient_sample\NIS_analytical_programs\NIS_101_export_tables.sas ![utf8_NIS_colon_cancer_table_1.xlsx](https://i.imgur.com/5mx0CVR.png) --- ### PROC REPORT setting #### By default, rows with missing values are removed from a table by PROC REPORT. To show the rows, specify `MISSING` option in the PROC REPORT. * [Example 11: How PROC REPORT Handles Missing Values](http://support.sas.com/documentation/cdl/en/proc/65145/HTML/default/viewer.htm#n0tnmdrpkks51vn0zw7o5hy3fzkq.htm) --- #### Table rows are sorted by PROC REPORT in an unknown manner. To order the table rows by values of a variable, specify `DEFINE variable/ order order=internal` * [ORDER, ORDER PLEASE: SORTING DATA USING PROC REPORT](https://support.sas.com/resources/papers/proceedings11/090-2011.pdf) --- ### Style the table of contetnts. This is to be done in MS Word, not in SAS. * [Text truncated in RTF Table of contents & changes of font face, size, color in TOC heading/body](https://communities.sas.com/t5/ODS-and-Base-Reporting/Text-truncated-in-RTF-Table-of-contents-amp-changes-of-font-face/m-p/577209) * [Creating Table of Contents in RTF Documents](https://support.sas.com/resources/papers/proceedings/proceedings/forum2007/097-2007.pdf) ### Style the title using STYLE function * [ODS ESCAPECHAR Statement](http://support.sas.com/documentation/cdl/en/odsug/65308/HTML/default/viewer.htm#p11xia2ltavr8ln17srq8vn4rnqc.htm) * To bold the first sentence, simply add `^{style [fontweight=bold]` to the beginning and `}` at the end "^{style [fontweight=bold]Table 1 Sources of data, sample sizes and types of analysis performed on substance use traits related to alcohol, caffeine, cannabis and nicotine.} UKB: UK Biobank. ICC: International Cannabis Consortium. GSCAN: GWAS & Sequencing Consortium of Alcohol and Nicotine use. OA: observational associations. GA: genetic analyses including SNP-based heritability estimation, genetic correlation, and two-sample Mendelian randomisation." ### RTF control strings #### Change font color of spanned headers using RTF control words * [SAS Notes and Concepts for ODS:The RTF Destination](https://support.sas.com/rnd/base/ods/templateFAQ/Template_rtf.html#control) * `\cfn` Specify **f**oreground **c**olor; n means color code (1=black,2=blue). I haven't ask Cynthia where to look up color code n * [ODS RTF: the Basics and Beyond](https://support.sas.com/resources/papers/proceedings11/263-2011.pdf) * [3 layers of spanned headers in PROC REPORT](https://communities.sas.com/t5/ODS-and-Base-Reporting/3-layers-of-spanned-headers-in-PROC-REPORT/td-p/341952) ```sas! /*Show spanned headers that start with GSCAN in black (default).*/ /*Show spanned headers that start with UKB in blue by '\cf2'.*/ column SNP_IDs gap01 /*insert a gap between columns with two-level headers*/ /*Significantly associated target and discovery traits by check marks in 3 sex groups*/ ("\brdrb\brdrdot\brdrw5\brdrcf1 GSCAN LD Clumping" GSCAN_LDClumped_si GSCAN_LDClumped_ai GSCAN_LDClumped_cpd GSCAN_LDClumped_sc GSCAN_LDClumped_dpw) gap02 ("\brdrb\brdrdot\brdrw5\brdrcf1\cf2 UKB LD Clumping" UKB_LDClumped_CPD UKB_LDClumped_PYOS UKB_LDClumped_ESDPW UKB_LDClumped_CCPD ) gap03 ("\brdrb\brdrdot\brdrw5\brdrcf1\cf2 UKB GCTA-COJO" UKB_COJO_CPD UKB_COJO_PYOS UKB_COJO_ESDPW UKB_COJO_CCPD ) ; ``` ### Border control #### Add a horizontal line under every value of a variable * [Horizontal lines in Proc Report](https://communities.sas.com/t5/ODS-and-Base-Reporting/Horizontal-lines-in-Proc-Report/td-p/100072) ```sas! /*Insert black horizontal lines (as borders) under every value of SNP_IDs*/ compute SNP_IDs ; call define(_row_,'style', 'style={bordertopcolor=black bordertopwidth=1}'); endcomp; ``` --- ### Import data with separators #### Import a tab-separated text file ```sas! /*File path of a tab-separated text file with 5 columns and 10 obs*/ filename table1 "D:\Now\library_genetics_epidemiology\GWAS\PRS_UKB_201711\GWASSummaryStatistics\GWAS_GSCAN\noQIMR_noBLTS_results\genetic_correlations\output_tabulated\rG_between-any-2-GSCAN-GWASs.tsv" encoding="utf-8" ; /*Read the file in*/ proc import datafile=table1 out=out.rG_bet_GSCAN_GWASs dbms=dlm replace; delimiter='09'x; getnames=YES; GUESSINGROWS=10; run; ``` --- ### A macro to select first non-missing value from people with multiple observations. For each elements of &variables, the macro outputs all the obs from input to a *_all dataset, outputs kept observations to a *_keep dataset, and outputs deleted observations to a *_drop dataset. The resulting *_keep dataset contains 1 observation per ID. ```sas! %SYSMACDELETE Select1stNonMissingDepVar; %macro Select1stNonMissingDepVar (input_data=,variables=); /*For each variable, select first non-missing values if there are multiple observations per ID. Output kept values to _keep data set. Output dropped observations to _drop dataset. Users need to combine all *_keep datasets by themselves*/ %do i=1 %to %sysfunc(countw(&variables.)) ; %let var_name=%scan(&variables., &i.) ; data tem.noMis1wav_&var_name._all /*3398 (inconsistent with 374+711+2275)*/ tem.noMis1wav_&var_name._keep /*2956*/ tem.noMis1wav_&var_name._drop ; /* 442*/ set &input_data.(keep=wave famID ID &var_name. age sex ageSq sexAge sexAgeSq zygosity); by ID; retain found ; if first.ID then found=0 ; /*Mark first ID with found=0*/ output tem.noMis1wav_&var_name._all; /*this data set contains all observations*/ /*Mark first non-missing obs (wanted) with found=1. Else marked by found=0*/ if found=0 AND (not missing(&var_name.) OR last.ID) then do; found=1; output tem.noMis1wav_&var_name._keep; /*this data set contains wanted observations*/ end; else output tem.noMis1wav_&var_name._drop; /*this data set contains unwanted observations*/ run; %put var_name=&var_name. ; %end; %mend Select1stNonMissingDepVar; %Select1stNonMissingDepVar(input_data=out.NU_nicotine_alcohol_disorder ,variables= FTND1 FTND2 FTND3 FTND4 FTND5 FTND6 FTND_sum stem_nic ); ``` ### Display Greek letters, symbols * [The Great Escape(char)](https://support.sas.com/resources/papers/proceedings10/215-2010.pdf) * [Unicode and Special Characters](http://support.sas.com/documentation/cdl/en/statug/68162/HTML/default/viewer.htm#statug_templt_sect019.htm) * [italicize a single character and superscript the next character for a column header ODS RTF](https://communities.sas.com/t5/ODS-and-Base-Reporting/italicize-a-single-character-and-superscript-the-next-character/m-p/493296) * Symbols and unicodes used. | SAS code |Symbol | | -------- | -------- | | '^{unicode 2192}' |ARROW, RIGHTWARDS| | '^{unicode beta}' |Greek beta| |'^{sub xy}' |~xy~| |'^{unicode 2713}'|check | | '^{style[fontstyle=italic]h}^{unicode 00B2}' |*h*^2^| | '^{unicode chi}^{unicode 00B2}' |*X*^2^| * Script: `D:\Now\library_genetics_epidemiology\slave_NU\NU_analytical_programs_tables\NU4tabSup01_exposure-effect-on-outcome_SNP-effect-estimates.sas` * Table location: `D:\Now\library_genetics_epidemiology\slave_NU\NU_analytical_output\NU4_tableSupplementary_byDate\MR1_tableSupp_2018-09-06.rtf` `D:\Now\library_genetics_epidemiology\slave_NU\NU_analytical_output\NU4_tables_byDate\NU4_tables_2018-09-10.rtf` ```sas! /*Add rightward arrows*/ proc report; column exposure_label gap01 bxy gap02 snp gap03 ("\brdrb\brdrdot\brdrw5\brdrcf1 Genetic variant ^{unicode 2192} Exposure" bzx bzx_se bzx_pval) /*ARROW, RIGHTWARDS 2192*/ gap04 ("\brdrb\brdrdot\brdrw5\brdrcf1 Genetic variant ^{unicode 2192} Outcome" bzy bzy_se bzy_pval) ; /*Add Greek letter beta and zy in subscript*/ %def_display(cVar=bzx,cName=^{unicode beta}^{sub zx},cWide=1 cm,headerAlign=right); /*Add a blank line under every exposure_label*/ compute after exposure_label ; line ""; endcomp; run; ``` ![](https://i.imgur.com/GLJO0ft.png) ```sas! /*Use unicode to show special character chi-squared*/ %def_display(cVar=X_squared_prevaBySex, cName=^{unicode chi}^{unicode 00B2}, cWide=1.5 cm, headerAlign=right); ``` ```sas! /*Add a special character for heritability h2*/ %def_display(cVar=h2_liab, cName=^{style[fontstyle=italic]h}^{unicode 00B2}, cWide=1 cm, headerAlign=right, colAlign=right); ``` --- #### Multiple layers of spanned headers * [3 layers of spanned headers in PROC REPORT](https://communities.sas.com/t5/ODS-and-Base-Reporting/3-layers-of-spanned-headers-in-PROC-REPORT/td-p/341952) --- #### Conditionally highlight rows based on values of a column. The condition variable, evaluated by IF statement, must be on the left of a COMPUTE block variable (to be highlighted). Duplicate a variable if the condition variable is on the right. * [Conditionally formatting rows in a rtf file based on the values of a column with COMPUTE block](https://communities.sas.com/t5/Statistical-Procedures/Conditionally-formatting-rows-in-a-rtf-file-based-on-the-values/td-p/581382) * script location: D:\Now\library_genetics_epidemiology\slave_NU\NU_analytical_programs_tables\NU4tab02_LDSC-genetic-correlation-results_licit-substance-exposure-GSCAN-UKB_outcome-cannabis-initiation-ICC.sas ![](https://i.imgur.com/H2sZWWb.png) ```sas! /*The COLUMN statement is used to list each report column*/ column OBSNO dup_rG_p_value gap01 ("\brdrb\brdrdot\brdrw5\brdrcf1 Trait 1" trait1_consortium trait1_substance trait1_name) gap02 ("\brdrb\brdrdot\brdrw5\brdrcf1 Trait 2" trait2_consortium trait2_substance trait2_name) gap03 ("\brdrb\brdrdot\brdrw5\brdrcf1 Genetic correlations" rG_esti rG_SE rG_Z_score rG_p_value) ; %def_display( cVar=rG_esti, cName=r^{sub G},isFmt=Y,cFmt=5.3, cWide=0.75 cm, headerAlign=right, colAlign=right); /*set conditional variables to noprint*/ define dup_rG_p_value /noprint; /*Bold rows if p values survive multiple testing 0.001724138 (0.05/29) condition variable dup_rG_p_value must be on the left of the COMPUTE block variable rG_esti call define(_col_) applies the format on the compute variable column-wide call define(_row_) applies the format across entire row*/ compute rG_esti; if dup_rG_p_value.sum < 0.001724138 then call define(_row_, 'style', 'style=[font_weight=bold]' ); endcomp; ``` #### Conditionally format a single or multiple columns based on the values of another (group of) column * [script location](D:\Now\library_genetics_epidemiology\slave_NU\NU_analytical_programs_tables\NU2tabSup07_fixed-effect-etimates-GSCAN-PRSs-on_illicit-drug-AU-CU-QIMR19Up.sas) ```sas! /*Example 1: conditionally bold a single column condition variable pvalueAge must be on the left of the COMPUTE block variable betaEstiAge call define(_col_) applies the format on the compute variable column-wide call define(_row_) applies the format across entire row*/ compute betaEstiAge /character ; if pvalueAge.sum < 0.05 then call define(_col_, 'style', 'style=[font_weight=bold]' ); endcomp; ``` ```sas! /*Example 2: conditionally format multiple columns. Present estimates in bold-face blue if the association p-values are lower than 0.0005882353*/ %macro conditionally_format_columns; /*20 columns to process*/ %let variables_to_format=%STR( si_fix_eff_estimate si_fix_eff_SE si_pvalue2sided si_R2 ai_fix_eff_estimate ai_fix_eff_SE ai_pvalue2sided ai_R2 cpd_fix_eff_estimate cpd_fix_eff_SE cpd_pvalue2sided cpd_R2 sc_fix_eff_estimate sc_fix_eff_SE sc_pvalue2sided sc_R2 dpw_fix_eff_estimate dpw_fix_eff_SE dpw_pvalue2sided dpw_R2) ; /*20 condition variables at which the 20 variables above are computed agicolumns to process*/ %let condition_variables= %STR(dup_si_pvalue2sided dup_si_pvalue2sided dup_si_pvalue2sided dup_si_pvalue2sided dup_ai_pvalue2sided dup_ai_pvalue2sided dup_ai_pvalue2sided dup_ai_pvalue2sided dup_cpd_pvalue2sided dup_cpd_pvalue2sided dup_cpd_pvalue2sided dup_cpd_pvalue2sided dup_sc_pvalue2sided dup_sc_pvalue2sided dup_sc_pvalue2sided dup_sc_pvalue2sided dup_dpw_pvalue2sided dup_dpw_pvalue2sided dup_dpw_pvalue2sided dup_dpw_pvalue2sided ) ; %let n_var2format=%sysfunc(countw(&variables_to_format.)); %let corrected_p_threshold=0.0005882353; %do i=1 %to &n_var2format.; %let var_01 = %scan(&variables_to_format., &i.); %let var_02 = %scan(&condition_variables., &i.); /*Here the columns to evaluate, referred by &var_02, are on the right of the COMPUTE block column, referred by &var_01. The following error if the columns to evaluate are on the left of the COMPUTE block variable ERROR 180-322: Statement is not valid or it is used out of proper order.*/ compute &var_01. ; if &var_02..sum < &corrected_p_threshold. then /*_COL_ is the column that is associated with the COMPUTE block variable*/ call define(_COL_, 'style', 'style=[foreground=blue font_weight=bold]' ); endcomp; %end; %mend conditionally_format_columns; %conditionally_format_columns ``` ![](https://i.imgur.com/rZQh1ZP.png) ---