I am attempting to create traffic lighting in a report using proc format. But even though the values can be greater than 1 or below 1 the colors are always the lowest color. In this case, they are all red. Why is SAS not seeing the values?
proc format;
value forecast
low - < 0.70 = 'red'
0.70 - <0.90 = 'yellow'
0.90 - high = 'green';
run;
%macro perform_target (cc, year, career_id, cc_name) ;
data Performance_&career_id._&cc._&year. ;
set post_target_comparisons1;
where institution = "&cc." and year = "&year." and career_id = "&career_id.";
run;
ods excel file = "Y:\General - CTE\2019 CTE Accountability\2020\Need Assessment Performance
Tables\Performance_&career_id._&cc._&year..xlsx" style = sasdocprinter;
ods excel options(autofilter="2-39" sheet_name = "Performance &year." embedded_titles = 'yes');
run;
title j= C "Actual to Target Comparisons";
title2 j = C "Academic Year - &year.";
run;
proc report data = Performance_&career_id._&cc._&year.;
column community_college cluster_label measure_label year total_students total_male percent_male
target forecast_male forecast_female forecast_AI forecast_AS forecast_AA forecast_HI forecast_PI
forecast_W forecast_MU forecast_disabled forecast_farms forecast_single forecast_displaced
forecast_ell forecast_nontrad;
define forecast_male/display 'Percent to Forecast Male' style(column) = [cellwidth=1in
tagattr="format:####.##\%" fontweight = bold foreground = forecast.];
run;
ods excel close;
%mend perform_target;
%perform_target (010042, 2016, 01);
This works so I expect your data is the issue.
proc format;
value forecast
low - < 0.70 = 'red'
0.70 - <0.90 = 'yellow'
0.90 - high = 'green';
run;
data have;
do x = 0 to 1 by .05;
output;
end;
run;
ods excel file='test.xlsx';
proc report data=have list;
columns x;
define x / display /*format=percent12.2*/ style(column)=[cellwidth=1in tagattr="format:####.##\%" fontweight = bold foreground = forecast.];
run;
ods excel close;
Related
I have a dataset in SAS in which the months would be dynamically updated each month. I need to calculate the sum vertically each month and paste the sum below, as shown in the image.
Proc means/ proc summary and proc print are not doing the trick for me.
I was given the following code before:
`%let month = month name;
%put &month.;
data new_totals;
set Final_&month. end=end;
&month._sum + &month._final;
/*feb_sum + &month._final;*/
output;
if end then do;
measure = 'Total';
&month._final = &month._sum;
/*Feb_final = feb_sum;*/
output;
end;
drop &month._sum;
run; `
The problem is this has all the months hardcoded, which i don't want. I am not too familiar with loops or arrays, so need a solution for this, please.
enter image description here
It may be better to use a reporting procedure such as PRINT or REPORT to produce the desired output.
data have;
length group $20;
do group = 'A', 'B', 'C';
array month_totals jan2020 jan2019 feb2020 feb2019 mar2019 apr2019 may2019 jun2019 jul2019 aug2019 sep2019 oct2019 oct2019 nov2019 dec2019;
do over month_totals;
month_totals = 10 + floor(rand('uniform', 60));
end;
output;
end;
run;
ods excel file='data_with_total_row.xlsx';
proc print noobs data=have;
var group ;
sum jan2020--dec2019;
run;
proc report data=have;
columns group jan2020--dec2019;
define group / width=20;
rbreak after / summarize;
compute after;
group = 'Total';
endcomp;
run;
ods excel close;
Data structure
The data sets you are working with are 'difficult' because the date aspect of the data is actually in the metadata, i.e. the column name. An even better approach, in SAS, is too have a categorical data with columns
group (categorical role)
month (categorical role)
total (continuous role)
Such data can be easily filtered with a where clause, and reporting procedures such as REPORT and TABULATE can use the month variable in a class statement.
Example:
data have;
length group $20;
do group = 'A', 'B', 'C';
do _n_ = 0 by 1 until (month >= '01feb2020'd);
month = intnx('month', '01jan2018'd, _n_);
total = 10 + floor(rand('uniform', 60));
output;
end;
end;
format month monyy5.;
run;
proc tabulate data=have;
class group month;
var total;
table
group all='Total'
,
month='' * total='' * sum=''*f=comma9.
;
where intck('month', month, '01feb2020'd) between 0 and 13;
run;
proc report data=have;
column group (month,total);
define group / group;
define month / '' across order=data ;
define total / '' ;
where intck('month', month, '01feb2020'd) between 0 and 13;
run;
Here is a basic way. Borrowed sample data from Richard.
data have;
length group $20;
do group = 'A', 'B';
array months jan2020 jan2019 feb2020 feb2019 mar2019 apr2019 may2019 jun2019 jul2019 aug2019 sep2019 oct2019 oct2019 nov2019 dec2019;
do over months;
months = 10 + floor(rand('uniform', 60, 1));
end;
output;
end;
run;
proc summary data=have;
var _numeric_;
output out=temp(drop=_:) sum=;
run;
data want;
set have temp (in=t);
if t then group='Total';
run;
How to plot the dynamic of the variable y over x in SAS (the easiest way!), for example, temperature changes over time. Thank you very much! (PC-SAS)
Two common ways are the scatter and series statements in Proc SGPLOT.
For example:
data have;
do time = 0 to 24;
time = time + ranuni(123) - 1.2;
temp = 60 + 6 * sin(time / 7.6);
rowNumber + 1;
output;
end;
run;
ods graphics on / width=325px;
proc sgplot data=have;
title "Scatter";
footnote "Created: &sysdate";
scatter x=time y=temp;
run;
proc sgplot data=have;
title "Series";
footnote "Created: &sysdate";
series x=time y=temp / markers;
run;
I'm using tagsets.excelxp in SAS to output dozens of two-way tables to an .xml file. Is there syntax that will suppress rows (frequencies and percents) if the frequency in that row is less than 10? I need to apply that in order to de-identify the results, and it would be ideal if I could automate the process rather than use conditional formatting in each of the outputted tables. Below is the syntax I'm using to create the tables.
ETA: I need those suppressed values to be included in the computation of column frequencies and percents, but I need them to be invisible in the final table (examples of options I have considered: gray out the entire row, turn the font white so it doesn't show for those cells, replace those values with an asterisk).
Any suggestions would be greatly appreciated!!!
Thanks!
dr j
%include 'C:\Users\Me\Documents\excltags.tpl';
ods tagsets.excelxp file = "C:\Users\Me\Documents\Participation_rdg_LSS_3-8.xml"
style = MonoChromePrinter
options(
convert_percentages = 'yes'
embedded_titles = 'yes'
);
title1 'Participation';
title2 'LSS-Level';
title3 'Grades 3-8';
title4 'Reading';
ods noproctitle;
proc sort data = part_rdg_3to8;
by flag_accomm flag_participation lss_nm;
run;
proc freq data = part_rdg_3to8;
by flag_accomm flag_participation;
tables lss_nm*grade_p / crosslist nopercent;
run;
ods tagsets.excelxp close;
D.Jay: Proc FREQ does not contain any options for conditionally masking cells of it's output. You can leverage the output data capture capability of the ODS system with a follow-up Proc REPORT to produce the desired masked output.
I am guessing on the roles of the lss and grade_p as to be a skill level and a student grade level respectively.
Generate some sample data
data have;
do student_id = 1 to 10000;
flag1 = ranuni(123) < 0.4;
flag2 = ranuni(123) < 0.6;
lss = byte(65+int(26*ranuni(123)));
grade = int(6*ranuni(123));
* at every third lss force data to have a low percent of grades < 3;
if mod(rank(lss),3)=0 then
do until (grade > 2 or _n_ < 0.15);
grade = int(6*ranuni(123));
_n_ = ranuni(123);
end;
else if mod(rank(lss),7)=0 then
do until (grade < 3 or _n_ < 0.15);
grade = int(6*ranuni(123));
_n_ = ranuni(123);
end;
output;
end;
run;
proc sort data=have;
by flag1 flag2;
*where lss in ('A' 'B') and flag1 and flag2; * remove comment to limit amount of output during 'learning the code' phase;
run;
Perform the Proc FREQ
Only capture the data corresponding to the output that would have been generated
ods _all_ close;
* ods trace on;
/* trace will log the Output names
* that a procedure creates, and thus can be captured
*/
ods output CrossList=crosslist;
proc freq data=have;
by flag1 flag2;
tables lss * grade / crosslist nopercent;
run;
ods output close;
ods trace off;
Now generate output to your target ODS destination (be it ExcelXP, html, pdf, etc)
Reference output of which needs to be produced an equivalent having masked values.
* regular output of FREQ, to be compare to masked output
* of some information via REPORT;
proc freq data=have;
by flag1 flag2;
tables lss * grade / crosslist nopercent;
run;
Proc REPORT has great features for producing conditional output. The compute block is used to select either a value or a masked value indicator for output.
options missing = ' ';
proc format;
value $lss_report ' '= 'A0'x'Total';
value grade_report . = 'Total';
value blankfrq .b = '*masked*' ._=' ' other=[best8.];
value blankpct .b = '*masked*' ._=' ' other=[6.2];
proc report data=CrossList;
by flag1 flag2;
columns
('Table of lss by grade'
lss grade
Frequency RowPercent ColPercent
FreqMask RowPMask ColPMask
)
;
define lss / order order=formatted format=$lss_report. missing;
define grade / display format=grade_report.;
define Frequency / display noprint;
define RowPercent / display noprint;
define ColPercent / display noprint;
define FreqMask / computed format=blankfrq. 'Frequency' ;
define RowPMask / computed format=blankpct. 'Row/Percent';
define ColPMask / computed format=blankpct. 'Column/Percent';
compute FreqMask;
if 0 <= RowPercent < 10
then FreqMask = .b;
else FreqMask = Frequency;
endcomp;
compute RowPMask;
if 0 <= RowPercent < 10
then RowPMask = .b;
else RowPMask = RowPercent;
endcomp;
compute ColPMask;
if 0 <= RowPercent < 10
then ColPMask = .b;
else ColPMask = ColPercent;
endcomp;
run;
ods html close;
If you have to produce lots of cross listings for different data sets, the code is easily macro-ized.
When I've done this in the past, I've first generated the frequency to a dataset, then filtered out the N, then re-printed the dataset (using tabulate usually).
If you can't recreate the frequency table perfectly from the freq output, you can do a simple frequency, check which IDs or variables or what have you to exclude, and then filter them out from the input dataset and rerun the whole frequency.
I don't believe that you can with PROC FREQ, but you can easily replicate your code with PROC TABULATE and you can use a custom format there to mask the numbers. This example sets it to M for missing and N for less than 5 and with one decimal place for the rest of the values. You could also replace the M/N with a space (single space) to have no values shown instead.
*Create a format to mask values less than 5;
proc format;
value mask_fmt
. = 'M' /*missing*/
low - < 5='N' /*less than 5 */
other = [8.1]; /*remaining values with one decimal place*/
run;
*sort data for demo;
proc sort data=sashelp.cars out=cars;
by origin;
run;
ods tagsets.excelxp file='/folders/myfolders/demo.xml';
*values partially masked;
proc tabulate data=cars;
where origin='Asia';
by origin;
class make cylinders;
table make, cylinders*n*f=mask_fmt. ;
run;
ods tagsets.excelxp close;
This was tested on SAS UE.
EDIT: Forgot the percentage piece, so this likely will not work for that, primarily because I don't think you'll get the percentages the same as in PROC FREQ (appearance) so it depends on how important that is to you. The other possibility to accomplish this would be to modify the PROC FREQ template to use the custom format as above. Unfortunately I do not have time to mock this up for you but maybe someone else can. I'll leave this here to help get you started and delete it later on.
I created the following graph using sgplot
proc sgplot data=Colordistricts;
hbar distrct/response=Percent
group= population;
run;
However, it seems that the individual population groups are arranged in alphabetical order in the graph (Asian followed by Black Color and White).
How do I create this same plot with the population groups in the descending order by percent?
In fact these are districts where the color population is highest. Basically I want to create a graph so that each bar begins with the color population
To force a specific group value to the first position you can map the desired group to a new value that will collate first. Sometimes this is easily done by placing a space character in front of an existing value.
If the group variable is a numeric ID custom formatted to display an associated group label you can create a new version of the custom format to include a 0 id that corresponds to the forced group. The forced group is mapped to the 0 id.
You would then sort the data in the particular way you need and use SGPLOT yaxis type=discrete discreteOrder=data; to force the hbar categories to appear in the particular order.
Here is some sample code to explore. The final SGPLOT uses the mapping technique to force a particular population segment to appear first.
ods html close;
%let path = %sysfunc(pathname(work));
ods html file="&path.\sgplot_hbar.html" gpath="&path.";
proc format;
value popId
0 = 'Color'
1 = 'Asian'
2 = 'Black'
3 = 'Color'
4 = 'White'
;
data have;
do _n_ = rank('A') to rank('P');
district = byte (_n_);
x = 0;
populationID = 2; percent = ceil(40*ranuni(123)); output;
x + percent;
populationID = 3; percent = ceil(40*ranuni(123)); output;
x + percent;
if (ranuni(123) < 0.10) then do;
populationID = 1; percent = ceil(10*ranuni(123)); output;
x + percent;
end;
percent = 100 - x;
populationID = 4;
output;
end;
keep district populationID percent;
label
percent = 'Percent of Total Frequency'
;
format
populationID popId.
;
run;
proc sgplot data=have;
hbar district
/ group = populationID
response = percent
;
title j=L 'default group order by populationID value';
title2 j=L 'districts (yaxis) also implicitly sorted by formatted value';
run;
proc sgplot data=have;
hbar district
/ group = populationID
response = percent
categoryOrder = respAsc
;
title j=L 'categoryOrder: ascending response';
title2 j=L 'districts (yaxis) also implicitly sorted by min(response)';
run;
proc sgplot data=have;
hbar district
/ group = populationID
response = percent
categoryOrder = respDesc
;
title j=L 'categoryOrder: descending response';
title2 j=L 'districts (yaxis) also implicitly sorted by descending max(response)';
run;
proc sql;
create table have2 as
select
case
when populationID = 3 then 0 else populationID
end as hbar_populationID format=popId.
, *
from have
order by
hbar_populationID, percent
;
quit;
proc sgplot data=have2;
yaxis type=discrete discreteOrder=data;
hbar district
/ group = hbar_populationID
response = percent
;
title j=L 'population seqment ordering is partially forced by tweaking populationID values';
title2 j=L 'districts in data order per yaxis statement';
run;
Forced groupOrder
SQL can sort the data in a particular order by using a case in the order by clause. You would then use groupOrder=data in SGPLOT.
proc sql;
create table have3 as
select *
from have
order by
district
, case
when populationID = 3 then 0
when populationID = 2 then 1
when populationID = 4 then 2
when populationID = 1 then 3
else 99
end
;
quit;
proc sgplot data=have3;
hbar district
/ group = populationID
groupOrder = data
response = percent
;
title j=L 'population seqment ordering is partially forced by tweaking populationID values';
title2 j=L 'districts in data order per yaxis statement';
run;
Forcing one segment to be first and then the other segments relying on response values
After mapping populationID 2 to 0 you could force the remaining population segments to be ordered similar to respAsc or respDesc. That process would require additional coding to determine new mappings for the other populationID values. This additional example shows how the global response sum is used to force a descending order on the remaining population segments within a district.
proc sql;
create table way as
select populationID, sum(percent) as allPct
from have
where populationID ne 3
group by populationID
order by allPct descending
;
data waySeq;
set way;
seq + 1;
run;
proc sql;
create table have3 as
select
have.*
, case
when have.populationID = 3 then 1000 else 1000+seq
end as hbar_populationID
from have
left join waySeq on have.populationID = waySeq.populationID
order by
hbar_populationID, percent
;
create table fmtdata as
select distinct
hbar_populationID as start
, put(populationID, popId.) as label
, 'mappedPopId' as fmtname
from have3;
quit;
proc format cntlin = fmtdata;
run;
%let syslast = have3;
proc sgplot data=have3;
yaxis type=discrete discreteOrder=data;
hbar district
/ group = hbar_populationID
response = percent
groupOrder = data
;
format hbar_populationID mappedPopId.;
title j=L 'population seqment ordering is partially forced by tweaking populationID values';
title2 j=L 'districts in data order per yaxis statement';
run;
title;
Here's a simple data set:
data dat;
do i = 1 to 100;
if rand('unif') > 0.85 then txt = 'DEFG';
else if rand('unif') > 0.75 then txt = 'ABC';
else if rand('unif') > 0.80 then txt = 'KLMNOP';
else txt = 'HIJ';
output;
end;
run;
I want to create a bar chart that displays the frequencies of txt:
proc sgplot data = dat;
/* Bars are ordered alphabetically */
vbar txt;
run;
This creates
What I'd like instead is to order the bars by height (from left to right: HIJ, ABC, DEFG, KLMN).
Is there an option to proc sgplot to achieve that?
You can add option categoryorder in your vbar statement:
proc sgplot data = dat;
vbar txt /categoryorder=respdesc ;
run; quit;
I found it here: http://blogs.sas.com/content/graphicallyspeaking/2012/06/07/bar-chart-with-response-sort/#prettyPhoto