Search code examples
statalocalstata-macros

A local within a local evaluates as empty in a program


I am trying to write a mini-program that simply takes a list of variables, and returns a sub-list that contains variables that are actually in the dataset.

I do this many times in a do file (not sequentially, so no loops), so it is easier to just have a quick program rather than effectively duplicate this code every time.

A simplified version of the program is below. The main issue appears to be in line 6. The first argument for the program should be the name of a local macro that contains variable names to compare with those in the dataset. So, for example, if the local macro is list1, the first argument of the program is list1, and I want to store a new local macro, vlist, which contains all the variables in list1.

But when I try to do this with:

``1'' 

the resulting local macro vlist just ends up being empty, while the local allvars is fine.

My program's code is the following:

    clear
    cap program drop lm
    program define lm
        * Create a new local, vlist, that is all the variables in the local macro identified in argument 1
        local vlist  ``1''

        * Create a new local, allvars, that is all variables in teh dataset
        qui ds, a
        local allvars `r(varlist)'

        * Display both macros, to illustrate that vlist is empty, while allvars contains all variables
        di as text "vlist:      " as result "`vlist'"
        di as text "allvars:    " as result "`allvars'" _newline

        * Create a new local macro that is the intersection of the two lists (if it worked)
        local `1'_inter : list vlist & allvars

        * Display different messages depending on the outcome (e.g. if a list was created, or empty)
        if missing("``1'_inter'") di as error "There are no common variables between `1' and the dataset."
        else di as result "`1' intersection with varlist _all now stored as local: `1'_inter"
        end

    clear
    input float(var1 var2 var3 var4 var5)   // Input irrelevant data
    . . . . .
    end

    /*  Next, create a macro with a list of variables that are in the dataset
        (e.g. var1 and var2), and even some that are not in teh data (var7). */ 
    local list1 var1 var2 var7

    * Execute the list-match program
    lm list1

As you can see, the local vlist ends up being empty, so there is no intersection between the lists.

Any idea what I am doing wrong here?

I am sure it is the double local macro, but I am not sure how to fix it.


Solution

  • The macro in macro reference

    ``1'' 
    

    is evaluated as follows. First, macro 1 is the first thing you typed as argument to the command, which in your example is list1, so substituting that Stata then sees

    `list1'
    

    and the entire command is now evaluated as

    local vlist `list1' 
    

    which is then evaluated as follows. What is in the local macro list1? Precisely nothing, or an empty string, as no such local macro exists at that point in the program (and any other local macros created anywhere else are invisible, because that is what local means: local to the program space concerned). So next Stata sees

    local vlist  
    

    whereby you simultaneously create a local macro and blank it out, thus destroying it.

    The solution is that you intended to write

    local vlist "`1'" 
    

    or perhaps just

    args vlist 
    

    However, that is not the end of it. The command

    lm list1 
    

    mentions the name of a local macro you have just created. But passing the name of a local macro to another program is futile, as the other program can't see the contents of the macro. You have to pass the contents.

    Yet more: the program then needs to be revised as you may want to pass several names to it and they will only be regarded as a single argument if you bind them in double quotes.

    FWIW, the problem you describe seems to be that solved by isvar from SSC. Here it is:

    *! NJC 1.0.0 20 Sept 2005 
    program isvar, rclass  
            version 8 
            syntax anything 
    
            foreach v of local anything { 
                    capture unab V : `v' 
                    if _rc == 0 local varlist `varlist' `V' 
                    else        local badlist `badlist' `v' 
            }
    
            di 
    
            if "`varlist'" != "" { 
                    local n : word count `varlist' 
                    local what = plural(`n', "variable") 
                    di as txt "{p}`what': " as res "`varlist'{p_end}" 
                    return local varlist "`varlist'" 
            }       
    
            if "`badlist'" != "" {
                    local n : word count `badlist' 
                    local what = plural(`n', "not variable") 
                    di as txt "{p}`what': " as res "`badlist'{p_end}" 
                    return local badlist "`badlist'" 
            }       
    end 
    

    I am fond of ds but I wouldn't use it here. You could be calling up thousands of variable names just to check whether a few names are in a dataset.

    A final comment on style

    local list1 var1 var2 var7
    lm list1
    

    might as well be

    lm var1 var2 var7
    

    as there is no value in putting the names into a bag (local macro) and using the name of the bag when you can just pass the names directly. As said, this won't work with your program until you fix it, but it would work with isvar.