Also known as [[Chang Y. Chung|]].
\nin a data step, expressions like: \n{{{\nx < y < z < a \n}}}\nis evaluated as in math. i.e., it is equivalent to:\n{{{\nx < y and y < z and z < a\n}}}\n\nin macro, i.e., within %eval() or %sysevalf(), comparison operators are left-associative, i.e., the above is equivalent to:\n{{{\n((x < y) < z) < a\n}}}\n\nthey are not the same. documentation is misleading, if not incorrect.\n
* %age -- see Ian and my paper on %ifn() at [[|]]\n* %zdiv -- by [[Kevin Myers|]]'s clever use of a pair of {{{ifn()}}} functions and polished by [[Dale McLerran|]] in a long thread started by [[Julie|]]
[[About]] \n[[MSOffice]] \n[[SAS]] \n[[Stata]] \n----\n[[Home|]] \n----\nTiddlyWiki \n<<newTiddler>> \n----
* Paper by Toby and me (a bit out-dated): at [[|]]\n* ODS HTML destination: [[SAS FAQ03357|]]\n* ODS PDF destination: (in version 9) [[SAS FAQ04449|]]\n* ODS RTF destination: (in version 9) [[SAS FAQ04450|]] \n* in fact, ODS PRINTER destinations now (in 9) supports {{{ {lastpage} in-line formatting function}}}. See [[New ODS PRINTER features for SAS 9.1|]]
This must have been asked frequently, since the has this FAQ entry written by none other than Gould himself.\n\nNow mata is available, I found doing this feels much closer to actually looping over arrays. In the following, I am looping over 60 variables from
... [[grok|]]'s Statistical Programming
/* first displayed */\ndiv.tiddler:first-child .title {\nfont-size: 28pt;\n}
Little tutorial on how to manage the template item stor's. First written on 2004-02-19.\n\nSee the content of the default item stor, sasuser.templat.\n{{{\nproc template;\n list;\nrun;\n}}}\n\nPrepare for updating (or creating if not exists already) the stor called myTmp in the work library.\n{{{\nods path work.myTmp(update)\n sasuser.templat(read)\n sashelp.tmplmst(read)\n;\n}}}\n\nCopy styles.default(in sasuser.templat) to myDefault (in work.myTemp). \n{{{\nproc template;\n edit styles.default as myDefault;\n end; /* <-- you need this */\nrun;\n}}}\n\nSee if it is in there:\n{{{\nproc template;\n list myDefault/store=work.myTmp;\nrun;\n/* on log\nListing of: WORK.MYTMP\nPath Filter is: myDefault\nSort by: PATH/ASCENDING\n\nObs Path Type\n---------------------------\n 1 myDefault Style\n*/\n}}}\n\nNow done. Delete myDefault from work.myTemp.\n{{{\nproc template;\n path work.myTmp(update);\n delete myDefault;\nrun;\n}}}\n\nDelete the item store, work.myTmp itself. First you restore the default ods path, otherwise deleting or the item stor would not work.\n{{{\nods path sasuser.templat(update) \n sashelp.tmplmst(read) \n; \nproc datasets lib=work;\n delete myTmp / mt=itemstor;\nrun;\nquit;\n}}}
is created by Jeremy Ruston. Visit [[|]]. Changarilla is an instance of TiddlyWiki <<version>>.\n
Learn and use proc transpose. Otherwise you will end up doing a lot of coding for nothing. This is an elegant piece of code from Howard Schreier's sas-l [[posting|]] on 2004-02-19, which demonstrates the power of proc transpose.\n\nTest data:\n{{{\ndata long;\n length type $8;\n input student evaluation type $ month;\ncards;\n1 10 bio 1\n1 12 bio 2\n1 9 Math 1\n1 10 Math 2\n2 18 bio 1\n2 16 bio 2\n2 3 Math 1\n2 4 Math 2\n;\nrun;\n}}}\n\nFirst construct a variable concatenating TYPE and MONTH:\n{{{\ndata fortranspose;\n set long;\n _name_ = trim(type)||'_'||put(month,z1.);\n run;\n}}}\n\nThen transpose:\n{{{\n proc transpose data=fortranspose out=wide(drop=_name_);\n by student;\n var evaluation;\n run;\n}}}\n\nResult:\n{{{\n Obs Student bio_1 bio_2 Math_1 Math_2\n\n 1 1 10 12 9 10\n 2 2 18 16 3 4\n}}}
A programming guru once said that "COM is Love." That's fine. But wouldn't you say that it is lovely to share ideas, problems, tips, and possibly big dreams? :-)\n\nWelcome to Changarilla, where Chang shares his stories about programming. Enjoy!
Sometimes we want to "see" the data as SAS's proc freq does. Here is my attempt to do this by wrapping up the contract command.\n{{{\ncap program drop bigTab\nprogram def bigTab\n version 8\n\n // we are going to destroy data in the memory\n preserve\n\n // most of the work is done in the following line\n contract `0'\n\n // add a line at the bottom\n expand 2 if _n == _N\n local N = _N\n\n // set variables at the bottom with missing -- an inefficient hack!\n cap ds\n foreach v of varlist `r(varlist)' {\n cap replace `v' = . in `N' // numeric\n cap replace `v' = """" in `N' // string\n }\n\n // calculate the total\n tempvar total\n egen `total' = sum(_freq)\n replace _freq = `total' in `N'\n\n // calculate percentages\n gen _pct = 100 * _freq / `total'\n\n // display\n drop `total'\n list, clean\n\n // automatic restore\nend\n}}}\n\nNow you can do something like below. Enjoy!\n{{{\nsysuse auto\nbigTab foreign turn\n}}}
!Just create two {{{gph}}}'s and do {{{gr combine}}}:\n{{{\n* list them\ngraph dir, detail\n\n* remove those in the memory\ngraph drop _all\n\n* or you can free up more memory and close any open dialog boxes with\ndiscard\n\n* remove those on disk, e.g. male.gph\nerase male.gph\n\n* use a data and create two .gph's\nsysuse uslifeexp, clear\nline le_male year, saving(male)\nline le_female year, saving(female)\n\n* and combine: col(1) puts the two top and bottom\ngr combine male.gph female.gph, col(1) \n\n* save it as a png format\ngr export grCombine1.png\n}}}\n[img[this |]]\n\n! Controlling {{{plot region}}} Aspect Ratio \n* Learn [[how graphs are constructed |]]\n* If used, the relative sizes will be based on the {{{min(width, height)}}} of the ''combined'' graph! -- so it does not help to use relative sizes in the component graphs.\n* Individual graph's aspect ratio can be best controlled ''indirectly'' with {{{xsize() ysize()}}}\n* Individual graph's {{{plot region}}} aspect ratio can ''directly'' controlled by {{{aspectratio()}}} option and this will remain even after combined.\n\n!Controlling Size of the Text and Markers\n* When combined, the size of text and markers are multiplied by the default {{{iscale()}}}, which is a function of //G//, the number of graphs combined.\n\n!{{{altshrink}}} option for the graph combine\n* According to Stata: {{{altshrink}}} specifies an alternative method of determining everything (text, markers, line thicknesses, and line patterns). When specified,\n<<<\nThe size of everything drawn on each individual graph is as though the graph were drawn at full size, but at the aspect ratio of the combined individual graph, and then the individual graph and everything on it were shrunk to the size shown in the combined graph.\n<<<\n\n! A Better Control\n* By combining the individual {{{aspectratio()}}} and combined {{{altshrink}}}, you can have a reliable control over the combined graph so that the individual graphs keep their original look and simply combined.\n{{{\ndiscard\nerase male.gph\nerase female.gph\n\n* use a data and create two .gph's\nsysuse uslifeexp, clear\n\n* square male\nline le_male year, saving(male) aspectratio(1) \n\n* 3 by 5 female\nline le_female year, saving(female) aspectratio(`=3/5')\n\n* and combine: col(1) puts the two top and bottom\ngr combine male.gph female.gph, col(1) altshrink \n\n* save it as a png format\ngr export grCombine2.png\n}}}\n[img[this |]]\n\n\n\n\n
If you have a numeric variable which indicates categories, then you probably has value labels attached to it. For example, the {{{foreign}}} variable in the {{{auto}}} data. Stata seems to think of this kind of numeric values as "codes." So, naturally, the commend you want to use to convert this numeric variable into a string variable is {{{decode}}}.\n{{{\nclear\nsysuse auto\nlabel var foreign "foreign"\n\n// convert it to a string variable\ndecode foreign, gen(strForeign)\nlabel var strForeign "strForeign"\ntab foreign strForeign, miss nolabel\n}}}\nThe above gives us:\n{{{\n | strForeign\n foreign | Domestic Foreign | Total\n-----------+----------------------+----------\n 0 | 52 0 | 52 \n 1 | 0 22 | 22 \n-----------+----------------------+----------\n Total | 52 22 | 74 \n}}}\n\nOf course, in version 9, we have plenty of ammunition to tackle it ourselves (which sometimes is needed to handle missing values and not-value-labeled values that {{{decode}}} ''ignores''(makes them all missing).\n{{{\ngen strForeign2 = ""\nlabel var strForeign2 "strForeign2"\nlevelsof foreign, missing local(levels)\nforeach lvl in `levels' {\n replace strForeign2 = "`:label(foreign) `lvl''" if foreign==`lvl'\n}\n}}}\nwhich gives us the same thing\n{{{\n | strForeign2\n foreign | Domestic Foreign | Total\n-----------+----------------------+----------\n 0 | 52 0 | 52 \n 1 | 0 22 | 22 \n-----------+----------------------+----------\n Total | 52 22 | 74 \n}}}\n\n
Stata macro references are immediately resolved. In addition, the non-existing macro references are resolved to nothing with no errors generated. Thus, the following attempt fails: \n{{{\nlocal initials My initials are `myinitials'\nlocal myinitials ABC\ndisplay "`initials'"\n// My initials are (nothing here)\n}}}\nThe reason why we don't see the initials(ABC) on display is that the myinitials are resolved in the first line, when the macro does not exist yet. If you want to change the myinitials value and wants this to be reflected in your initials, then you have to delay the resolution of myinitials. Can this be done?\n\nYes, of course. Just hide the back-tick from Stata until desired:\n{{{\nlocal initials My initials are \s`myinitials' \nlocal myinitials ABC\ndisplay "`initials'"\n// My initials are ABC\n\nlocal myinitials CYC\ndisplay "`initials'"\n// My initials are CYC\n}}}
Stata reads a command from left to right and certain characters (macro expansion punctuations, {{{`}}} and {{{$}}}) trigger macro substitution. You prevent this happening by "escaping" the punctuations by leading backslash ({{{\s}}}). There are four rules (which used to be cleaner upto release 8. With 9, it is less clean.)\n\n|! You type: |! Stata sees: |! note|\n| {{{\s$}}} | {{{$}}} | i.e., stata sees a non-macro-trigger, just a character {{{$}}} |\n| {{{\s`}}} | {{{`}}} | ditto |\n| {{{\s\s}}} | {{{\s}}} | only when the second backslash precedes {{{`}}} or {{{$}}} |\n| {{{\s}}} | {{{\s}}} | after applying the above rules first |\n\nThe above are from stata [[FAQ|]] and the item number 29 in the Programming section of [[What'sNew8to9|]].\n\nGet it? It is easy, just remember stata does not go back and do recursive resolution at all. just one pass with look-ahead, that's it. Suppose that you already did:\n{{{\nlocal myMac "ABC"\n}}}\nWhat does stata print out, when you do the following? \n{{{\ndisplay `"\s\s\s`myMac'"'\n}}}\nStata spits back the following -- because first slash remains as slash and the second and third slashes become one slash since they are immediately followed by a back tick.\n{{{\n\s\sABC\n}}}\nNow, guess what the following will display?\n{{{\ndisplay `"\s\s\s\s`myMac'"'\n}}}\nYes, of course. Three slashes since the first two slashes remain as they are.\n{{{\n\s\s\sABC\n}}}\nNow, how about this one?\n{{{\nlocal quoted "\s`myMac'"\nlocal `quoted' "D"\ndisplay `" `quoted' "' \ndisplay `" ``quoted'' "'\n}}}\nThe answers are:\n{{{\nABC\nD\n}}}\nThis is because even though the macro trigger can be masked when it is first being evaluated, stata macro does not store it. The second one is just a straight-forward double resolution. See the output from macro list. (preceding underscore character means a local macro variable -- this is how stata knows what is local and what is global, simple, huh?)\n{{{\n. macro list\n...\n_ABC: D\n_quoted: `myMac'\n_myMac: ABC\n...\n}}}
In ms excel find-and-replace, the asterisk {{{*}}} and the question mark {{{?}}} are wild characters, matching any and single characters, respectively. In order to escape them, use the tilde {{{~}}}. In order to escape the tilde, double it up. That is,\n\n|! to match this character |! use this |\n| {{{*}}} | {{{~*}}} |\n| {{{?}}} | {{{~?}}} |\n| {{{~}}} | {{{~~}}} |\n\nThe help page within excel regarding this is titled "Find or replace text and numbers on a worksheet." (Excel 11 (2003) sp1)
You need ''sas/access interface to ole db'' to do this. Basically it bypasses the service, and connects directly to Jet provider.\n\n{{{\nlibname e oledb \n provider="Microsoft.Jet.OLEDB.4.0" \n properties=('Data Source'='d:\schang\sg\stest.xls') \n provider_string="Excel 8.0;hdr=no" \n preserve_tab_names=yes ; \n%put &sysdbmsg; /* any message returned? */\n\ndata one; \n set e.'Sheet1$a1:b3'n( \n dbmax_text=32767 \n dbsastype=( \n f1='char(4)' \n f2=numeric ) ); \n rename f1=name f2=v2; \nrun;\n\nproc print data=one; /* always check */ \nrun;\nproc contents data=one;\nrun;\n\nlibname e clear; /* prevent sas from locking the excel file up */\n}}}\n\nThe above example reads data from the range a1 to b3 in the c:\schang\sg\stest.xls workbook. Column A becomes a character variable, name (length=$4) and column b a numeric variable, v2. Notes are in the below:\n\nCapability \n# reading, updating, creating, and deleting tables possible, through this approach. \n# adding or deleting columns are not. use sql pass-through facility, instead. \n# cannot control excel application either. Try sashelp.fsp.hauto class for OLE Automation in scl.\n\nLibname \n# if this excel file is already open, then sas returns the, confusing, "file not found" error when you set in the datastep. One way to prevent it is to make the excel workbook shared (see in the below). \n# Excel 8.0 works for both 97 and 2000 .xls format. (works for 2002, too) \n# hdr=yes means that the first row has the variable name. \n# preserve_tab_names=yes works with 'sheet1$a1:b2'n style table name. \n# sysdbmsg returns some message when de-referenced immediately after attemping a connection, as we do with libname.\n\nData Step \n# an excel sheet name can only be upto 31 characters long. \n# a max range for an excel sheet is a1:iv65536. \n# dbmax_text=32767 prevents truncation of a possiblly lengthy character values in the cell. \n# dbsastype designates the type for the SAS variable created. Conversion will be done in the database side -- this will minimize loss of precision. Other types are: numeric, datetime, date, or time. When you write to an excel sheet, use dbtype= option to control how SAS converts values into oledb type. (defaults are ~DBTYPE_STR(size) or ~DBTYPE_R8.) default var names are f1, f2, ...
Suppose that you are assigning a long string to a macro variable. Remember that stata's expression evaluator silently //truncates// a string (the max is 244 chars). This leads to a pretty subtle error.\n{{{\nclear\n// create a test dataset with a lot of variables\nsysuse auto\nds\nlocal varlist "`r(varlist)'"\nforeach var of local varlist {\n rename `var' `var'0\n forvalues n = 1/5 {\n gen `var'`n' = `var'0\n }\n}\n\n// here things getting interesting\n// this works fine!\nds\nlocal long "`r(varlist)'"\nforeach var of local long {\n di "`var'"\n}\n\n// here the variable list is truncated because the local statement invokes the expression evaluator\nds\nlocal short = "`r(varlist)'"\nforeach var of local short {\n di "`var'"\n}\n}}}
!Vince Wiggin's NASUG 2004 Presentation\nRead Vince Wiggins's presentation at the 3rd [[NASUG(2004)|]] -- the presentation materials can be installed using the {{{net}}} command.\n{{{\nnet from\nnet describe boston04\nnet install boston04\n\nbgrtalk\nwhelp bgrtalk\n}}}\n\n!Some extracts from the Vince talk\n!!What?\n* are the {{{Stata graphs}}}:\n> nested collections of class-system objects, containing sub-objects including {{{styles}}}, being capable of drawing themselves, containing scripts for reproducing themselves\n* is the {{{graph}}} command:\n> a not-so-thin parsing and object creation engine that creates objects in the class system\n* are {{{saved graphs}}}:\n> data in [[serset|]], log of commands to re-create the graph (a story), and sundry information to identify the graph\n\n!!Anatomy1 -- name of the parts\n|[[anatomy|]]|\nYou can manipulate through options, and nested options:\n{{{\n// change the title to a huge red, "New title" \n... title(New title, color(red) size(huge))\n}}}\n\n!!Anatomy2 -- {{{.Graph}}} and other objects \n|[[graph objects|]]|\nYou can directly manipulate each objects with a method call like {{{.editstyle}}}:\n{{{\n// changing the style of the title\ color(red) size(huge) editcopy\ngraph display\n\n// changing the fill color of an area plot\ area(shadestyle(color(green))) editcopy\ngraph display\n}}}\n
Stata has neat odbc commands.\n\n{{{odbc list}}} shows available dsn:\n{{{\n. odbc list \n\nData Source Name Driver\n-------------------------------------------------------------------------------\ndBASE Files Microsoft Access dBASE Driver (*.dbf, *.ndx\nExcel Files Microsoft Excel Driver (*.xls, *.xlsx, *.xl\nMS Access Database Microsoft Access Driver (*.mdb, *.accdb)\n-------------------------------------------------------------------------------\n}}}\n\n{{{odbc query}}} (with {{{dialog}}} option will bring up a dialog box to see the available tables in the dsn.\n{{{\n. odbc query "Excel Files", dialog(prompt)\n\nDataSource: Excel Files\nPath : C:\sUsers\scchung\sDesktop\sBook1.xls\n-------------------------------------------------------------------------------\nSheet1$\nSheet2$\nSheet3$\n-------------------------------------------------------------------------------\n}}}\n\n{{{odbc desc}}} will look into the table and present variables.\n{{{\n. odbc desc "Sheet1$", dialog(complete)\n\nDataSource: Excel Files (query)\nTable: Sheet1$ (load)\n-------------------------------------------------------------------------------\nVariable Name Variable Type\n-------------------------------------------------------------------------------\nvar1 VARCHAR\nvar2 NUMBER\nvar3 NUMBER\n-------------------------------------------------------------------------------\n}}}\n\nFinally, try {{{odbc load}}} with {{{table}}} and {{{dialog}}} options to load the data in.\n{{{\n. odbc load, table("Sheet1$") dialog("complete")\n\n. list\n\n +---------------------+\n | var1 var2 var3 |\n |---------------------|\n 1. | A 12 21.43 |\n 2. | B 324 98.34 |\n 3. | C 2231 987 |\n +---------------------+\n\n. des\n\nContains data\n obs: 3 \n vars: 3 \n size: 792 (99.9% of memory free)\n-------------------------------------------------------------------------------\n storage display value\nvariable name type format label variable label\n-------------------------------------------------------------------------------\nvar1 str244 %244s \nvar2 double %10.0g \nvar3 double %10.0g \n-------------------------------------------------------------------------------\nSorted by: \n Note: dataset has changed since last saved\n}}}
|>|! Golden Rule|\n| Never code | {{{... `a'^2 ...}}} |\n| Do code |{{{... (`a')^2 ...}}} |\n\nThis is because a change that stata made (from version 4 to 5) in evaluating expressions involving the power operator ({{{^}}}). I ''hate'' stata for making this change, since the burden is on the programmer to remember to use the parens. Gould explains why this has happened in a Stata [[FAQ|]].
\n| ! property name | ! default value |! notes |\n| {{{name}}} | (none) |1-32 char long; case sensitive; letters, digits, and underscore; stata's built-in names start with an underscore; other reserved names are: {{{byte, double float if in int long using with}}}; can be abbreviated; {{{~}}} in varname means zero or more chars |\n| {{{(storage) type}}} | {{{float}}} |storage types: {{{byte, int, long, float, double, str1-str244}}} |\n| {{{label}}} | (blanks) |variable label upto 80 character-long; |\n| {{{value label}}} | (none) |only an integer or an extended missing values({{{.a, .b, ..., .z}}}) can be associated with a value label; [[numlabel|]] |\n| {{{(display) format}}} | for byte and int {{{%8.0g}}}, long {{{%12.0g}}}, float {{{%9.0g}}}, double {{{%10.0g}}}; for str{{{w}}} type: the wider of {{{%9s}}} and {{{%ws}}} |default depends on the storage type. only a //display// format. |\n| {{{characteristics}}} | (nothing) |[[notes|]] are implemented using [[char|]]. all lower case char names are reserved by stata corp. {{{char list}}} to list all the existing characteristics. {{{di "`:char make[]'"}}} to see the name of the characteristics attached to the variable, //make//. {{{di "`make[note1]'"}}} to see the content of the first note attached to the var, //make//. |\n\nThe [[clonevar|]] command ado source tells us all the variable properties. It is short and sweet.\n{{{\n. type "c:\sprogram files\sstata9\sado\sbase\sc\sclonevar.ado"\n*! version 1.0.1 13oct2004 \nprogram clonevar \n version 8.0 \n gettoken newvar 0 : 0, parse("= ") \n gettoken eqs 0 : 0, parse("= ") \n gettoken varname 0 : 0 \n syntax [if] [in] \n\n if "`eqs'" != "=" {\n di "{p}{err}syntax is {cmd:clonevar {it:newvar} = {it:varname}} ...{p_end}"\n exit 198\n }\n\n confirm new var `newvar' \n \n confirm var `varname'\n\n \n local type : type `varname'\n gen `type' `newvar' = `varname' `if' `in' \n \n local w : variable label `varname'\n if `"`w'"' != "" label variable `newvar' `"`w'"'\n \n local vallbl : value label `varname' \n if "`vallbl'" != "" label val `newvar' `vallbl' \n \n format `newvar' `: format `varname''\n \n tokenize `"`: char `varname'[]'"' \n while `"`1'"' != "" {\n char `newvar'[`1'] `"`: char `varname'[`1']'"' \n mac shift \n }\nend\n}}}\n\nStata manual [P] ''char'' section does the above task with the following. Notice the use of the {{{char rename}}} instead of looping through. I quote:\n<<<\nThe purpose of the program is to change the contents of one of the variables in the user's data. The programmer worry about the user pressing //Break// while the program is in the midst of the change, so you properly decide to construct the replaced values in a temporary variable and, only at the conclusion, drop the user's original variable and replace it with the new one. In this example, macro {{{`uservar'}}} contains the name of the user's original variable. Macro {{{`newvar'}}} contains the name of the temporary variable that will ultimately replace it.\n<<<\n{{{\n version 9\n ...\n tempvar newvar\n ...\n ( code creating `newvar')\n ...\n local varlab : variable label `uservar'\n local vallab : value label `uservar'\n local format : format `uservar'\n label var `newvar' "`varlab'"\n label values `newvar' `vallab'\n format `newvar' `format'\n nobreak {\n char rename `uservar' `newvar'\n drop `uservar'\n rename `newvar' `uservar'\n }\nend\n}}}
This question came up in 2003 on stata listserver: by someone named [[laszlo|]]. Two answers: by [[Nick Cox|]], and by [[Bill Gould|]]. The latter seems to be worth repeating here:\n{{{\nThe point of c_local is to be a tool for developing programming-language \nstatements. For instance, imagine I wanted to create a new command, \n\n bill_gould x : ...\n\nto be used in other programs. The point of the new -bill_gould- command \nis to assign something to the local macro x. Inside bill_gould.ado, I\nwould code the line\n\n c_local `1' ...\n\nThus assigning back to the local macro in the caller's space.\n\n-- Bill\\n}}}\nToday, someone told me about this command as a good way of making local macro variables (defined in a selected-and-then-submitted section of a do file) "stick" to the console session -- which is an ingenious way of using {{{c_local}}}.
An interesting point made by a "little birdie" -- an annonymous sas institute insider answering to the questions posted on sas discussion group, sas-l on [[April 1st, 2004|]] -- (it was not a joke!)\n\n----\nCan someone explain what SAS resolves {{{compress('')}}} as?\n----\n{{{''}}} is a single blank.\n\n{{{compress('')}}} removes all blanks from {{{''}}} and therefore returns a string containing zero characters.\n\n{{{put(compress(''),$hex.)}}} returns two blanks because the {{{$hex.}}} format seems to have a minimum field width of two. Since there are no data to put into the field, the field is padded with blanks.\n\nWhen you compare two strings with the {{{=}}} operator, the shorter string is padded with blanks out to the length of the longer string.\n\nIt seems that the behavior of the {{{=:}}} operator is not clearly documented. An expression of the form {{{X=:Y}}} compares a prefix of {{{X}}} with a prefix of {{{Y}}}. The length of the prefix of {{{X}}} is: {{{min( length of X, max(1, length of Y) )}}} where "length" means the number of characters including trailing blanks. The length of the prefix of Y is: {{{min( length of Y, max(1, length of X) )}}} Furthermore, if one prefix is shorter than the other, it is not padded with blanks, and prefixes of unequal length are considered unequal to each other. This expression: {{{compress('')=:''}}} returns false because {{{compress('')}}} has a length of zero but {{{''}}} has a length of one.\n\nIf you're wondering why all this works the way it does, it's because the person who originally wrote the {{{DATA}}} step ''disliked'' strings with a length of zero, but we later discovered that there are times when people need zero-length strings.\n----