Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • How to stop a -replace- command if no changes are made (not in a loop)?

    Using excel, I created a long list of -replace- commands (something like 4,000). I then copy-pasted these into a do-file. I want to run all the commands, but for the code to stop if the -replace- command didn't make any real changes. This is to help me error check; all the -replace- commands should make a change, so if it doesn't that tells me something was messed up in excel. However, there are too many instances for me to manually read through all the return messages.

    ALTERNATIVELY, if it is possible to generate a list of all commands that didn't make any real changes, that would be better. But I imagine that might be beyond Stata's language?

    Advice? Most prior posts I see on this topic ask it in the context of a loop, but I'm not looping.

    Thanks

  • #2
    I can't think of a really simple way to do this. But here's a complicated way. I assume that your do-file with the -replace- commands is called replace_commands.do. I also assume, crucially, that each command fits on a single line. So if that is not currently the case, please remove any line breaks within commands on that file--the code won't work correctly unless each command fits on a single line. To program around this limitation and accept multi-line command would be really complicated.

    Code:
    sysuse auto, clear
    tempfile copy
    save `copy'
    
    file open commands using replace_commands.do, read text
    file open no_replace using no_replacements.do, write text replace
    
    file read commands command
    while r(eof) == 0 {    // LOOP THROUGH COMMANDS IN replace_commands.do
        capture `command'
        capture cf _all using `copy'
        if c(rc) == 0 {    // NO CHANGES WERE MADE
            file write no_replace `"`command'"' _n
        }
        else if c(rc) == 9 {    // ChANGE MADE
            quietly save `copy', replace
        }
        else    { // UNEXPECTED ERROR
            display as error "Unexpected error"
            exit c(rc)
        }
        file read commands command
    }
    file close commands
    file close no_replace
    
    type no_replacements.do
    Replace the -sysuse auto, clear- command at the top by a command to -use- the data file you want to run the replacement commands on.
    This code will loop through the replace_commands.do file one line at a time, executing each replacement command. After each command, it compares the data in memory to a copy of the file that was saved the last time the file was changed. If nothing was changed, the files will agree and the code will copy the command into the file no_replacements.do. If something was changed, the files will not agree, and the code will save the revised file and move on to the next command. Execution ceases when the last line of the replace_commands.do file is read.

    Finally, the contents of the file no_replacements.do (which has all of the commands that didn't actually replace anything) will be typed to the Results window. That file is also saved in your working directory as no_replacements.do, so you can open it in the do-editor or any other text editor if you wish.

    Note that if the -replace- command contains an error, so that it does not actually run, it is treated by this code as a command that made no changes: it is included in the final output and in the no_replacements.do file.

    I have tested this code using the auto.dta set and with the following contents of file replace_commands.do:
    Code:
    replace price = 5000 if price > 10000
    replace rep78 = 5 if missing(rep78)
    replace foreign = 0 if rep78 == 1
    replace make =
    replace make = ""
    In the test, it correctly identifies the third and fourth commands, the former because it changes nothing, and the latter because it has a syntax error and doesn't run. The other commands all change something and do not appear in the output.

    So I think this does what you want.

    Comment


    • #3
      Thanks!

      So for this to work, the replace codes need to be in a separate do file?

      Comment


      • #4
        Yes, Clyde assumes your commands are in a separate, stand-alone file with one command per line.

        An alternative approach might use indicators and the `assert` command. This checks whether "changes" will be made, but does not check whether they are really changes. Syntax errors are going to bring everything to a halt.

        Depends on how thorough you are trying to be - this is a routine that will be run on multiple data sets, and produce differing results?

        Code:
        sysuse auto
        generate flag = (foreign==3)
        quietly summarize flag, meanonly
        assert r(mean)~=0
        replace x = 3 if flag // (foreign==3)
        Doug Hemken
        SSCC, Univ. of Wisc.-Madison

        Comment


        • #5
          How do you guys see the value of each error? For instance, how do you know
          c(rc) == 0 means no changes?

          Comment


          • #6
            Read -help cf- and you'll see how it works. It compares the data in memory to a Stata data file. If they are the same, it returns 0, if they differ, it returns 9. In the code in #2 the data in memory have just been operated on by the most recently tried command, and the data file is the data as it stood after the last command that made a change. So if the data in memory match, that means that the last command tried didn't change it.

            Comment


            • #7
              I'm adapting this code to my code. However, it seems like my replace codes that change the string type (eg str12 to str43) are returning _rc==0 even if they make changes.
              Last edited by Tommie Thompson; 23 Feb 2018, 10:11.

              Comment


              • #8
                If all the replace command does is change the string type, it isn't actually changing any of the information in the data set, so, no, my code will not flag that as an actual change in the data. But how is that even happening? What -replace- command do you have that changes the string type but does not change any content in the string variable? I can't think of one that would do that? Can you show an example of this?

                Comment


                • #9
                  The replace code changes the string type and changes content, but returns _rc==0

                  Comment


                  • #10
                    Please show data and code examples for this, and I will troubleshoot. That shouldn't happen.

                    To be clear, the -replace- command itself will return _rc = 0 as long as it runs successfully (which probably means nothing more or less than that it has no syntax errors). My code in #2 does not look at the return code from the -replace- command. It looks at the return code from the -cf _all- command. If you have, in adapting my code to your situation, changed it so that you are checking the return code from the -replace- command then you have changed the logic fundamentally, and it doesn't surprise me that it won't work properly.

                    My code in #2 is designed, as requested in #1, to detect -replace- commands that do not actually change the data. I still think it works correctly, but if you have an example where it does not, please show that, and I will try to fix it.
                    Last edited by Clyde Schechter; 23 Feb 2018, 10:31.

                    Comment


                    • #11
                      Thanks. I will try to figure out a non-confidential example later.

                      In the meantime, is there a way to do this using _rc? Like a loop that captures line 1, then displays the line if _rc==0? I'm not that familiar with -file-, but I'm thinking something like

                      capture `command'
                      if _rc==0 {
                      di "`command'"
                      }

                      Comment


                      • #12
                        Assuming that local macro command contains a command, the code you show in #11 should do what you say. It's not clear to me what you're doing here, though. You are looking to run a command and suppress its output, and display the command if it runs successfully, but move on without displaying the command if it fails. Seems an odd thing to do, but....

                        Comment


                        • #13
                          It dawns on me that I may have misunderstood initially what you are working with. I took your initial post to mean that you had a long list of -replace- commands, each of which should result in some data change, and that these were in some do-file waiting to be run as a block, and you wanted to trap and identify any that didn't result in data change. The first clue that this is not your situation was when you queried whether the commands to be tested needed to be in a separate do-file.

                          So let me reimagine your context. Perhaps you have a do-file, and interspersed within that do-file there are various commands that should result in a change in the data. But they are not all of the commands in the do-file, and perhaps not even in a continguous block. They occur scattered throughout the program. In that case, a different approach is needed. So, I would begin with putting the following program at the top of the code
                          Code:
                          capture program drop does_it_change_data
                          program define does_it_change_data, rclass
                              macro shift
                              local command `0'
                              tempfile copy
                              quietly save `copy'
                              capture `command'
                              return scalar success = !c(rc)
                              if c(rc) {
                                  display as result `"`command'"' as error " was unsuccessful"
                              }
                              capture cf _all using `copy'
                              return scalar data_change = !!c(rc)
                              if c(rc) == 0 {
                                  display as result `"`command'"' as error " did not change data"
                              }
                              
                              exit
                          end
                          Then, for any command that you want to check for data change, you can just precede the command by does_it_change_data. The program will then run the command and test whether or not the data has been changed by it. It will also notice whether the command ran successfully at all. If the command does not run successfully, or if it runs successfully but leaves the data unchanged it will print an error message in the output. The command also returns two scalars in r(), r(success) which is 1 if the command executed successfully, 0 otherwise; and r(data_change) which is 1 if the command changes the data, and 0 otherwise. You can apply this to any kind of command anywhere in the do-file, not just -replace- commands. And the commands are in the current do-file and do not have to be continguous. You can also, after such a command, take different actions depending on the values returned in r(success) and r(data_change). Here's how it would look in use:

                          Code:
                          sysuse auto, clear
                          
                          does_it_change_data replace rep78 = 5 if missing(rep78)
                          return list
                          
                          does_it_change_data replace rep78 = rep78
                          return list
                          
                          does_it_change_data replace rep78 =
                          return list

                          Comment


                          • #14
                            If you are processing thousands of these replace calls, I assume that you would prefer not stopping the do-file if you encounter a replace that makes no change and simply record the ineffective command somewhere. In the following example, I write the ineffective command to a dataset note. You can then review all cases by simply listing notes.

                            You can save the myreplace program in a file called "myreplace.ado" somewhere Stata will find it (see help adopath) or simply include it at the top of the do-file as I do here.

                            Code:
                            clear all
                            sysuse auto
                            
                            program myreplace
                            
                                version 9
                                
                                gettoken vname rest : 0
                                tempvar old
                                qui clonevar `old' = `vname'
                                replace `0'
                                qui count if `old' != `vname'
                                if r(N) == 0 note `vname': no change when using => {bf}replace `0' {sf}
                            end  
                            
                            myreplace price = 5000 if price > 10000
                            myreplace rep78 = 5 if missing(rep78)
                            myreplace foreign = 0 if rep78 == 1
                            myreplace make = proper(make)
                            myreplace make = subinstr(make,".","",.)
                            myreplace make = subinstr(make,",","",.)
                            myreplace make = subinstr(make,":","",.)
                            myreplace make = lower(make)
                            
                            notes
                            and the results
                            Code:
                            . notes
                            
                            _dta:
                              1.  from Consumer Reports with permission
                            
                            make:
                              1.  no change when using => replace make = subinstr(make,",","",.)
                              2.  no change when using => replace make = subinstr(make,":","",.)
                            
                            foreign:
                              1.  no change when using => replace foreign = 0 if rep78 == 1
                            
                            .

                            Comment

                            Working...
                            X