Announcement

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

  • Adding space in a tokenized token for units based on criteria

    Hello all:

    I was writing a wrapper to generate multiple variable medians with their min, max and IQR formatted to display with their units. I managed to tokenize the units for each of the variables in order from a passthru in the syntax with a parallel loop. However, I am unable to decide how I can loop in a conditional statement to add a space before the unit text if the unit has a match for any alphabet/text (for e.g. "cm", "years" and such, as opposed to %). I played with some if loops/regexm but can't get the ``u'' to be read with a space if the text matches alphabets. The line below containing
    ret local `v'_miqr has the ``u''
    Code:
    *! program dmed v1.0.0 GV 8nov2023
    sysuse auto, clear
    
    cap program drop dmed
    return clear
    
    program define dmed, rclass
        version 18.0
        set trace on
        syntax varlist(numeric max=7) [if] [, unit(passthru)]
        marksample touse
        qui count if(`touse')
            if "`unit'" != "" {
                di "unit is: `unit'"
                tokenize `unit', parse("() ")
            }
            else {
                di "unit() is empty"
            }
        return local tot = `r(N)'
        local endash = ustrunescape("\u2013")
        
    // Clyde code to reassign formatting for decimal variables below
    *---------------------------------------------------------
    local u = 3
    foreach v of local varlist {
        capture confirm float var `v', exact
            if c(rc) == 0 {    
                    local dec "%3.1f"
                } 
            else {
            local dec "%8.0g"
            }
        tabstat `v' if(`touse'), statistic(p25 p50 p75 min max) save
                mat list r(StatTotal)
                matmap r(StatTotal) B, map(round(@, 0.1))        // Bad idea. Dont round up.
                mat list B
                    local `v'lqr      = strltrim("`:di `dec' `=B[1,1]''")
                    local `v'med     = strltrim("`:di `dec' `=B[2,1]''")
                    local `v'uqr      = strltrim("`:di `dec' `=B[3,1]''")
                    local `v'min     = strltrim("`:di `dec' `=B[4,1]''")
                    local `v'max      = strltrim("`:di `dec' `=B[5,1]''")
                    ret local `v'_miqr         `"``v'med'``u'' (IQR: ``v'lqr'`endash'``v'uqr'``u'')"'
                    ret local `v'_mr         `"``v'med'``u'' (range: ``v'min'`endash'``v'max'``u'')"'
                    ret local `v'lqr      = strltrim("`:di `dec' `=B[1,1]''")
                    ret local `v'med     = strltrim("`:di `dec' `=B[2,1]''")
                    ret local `v'uqr      = strltrim("`:di `dec' `=B[3,1]''")
                    ret local `v'min     = strltrim("`:di `dec' `=B[4,1]''")
                    ret local `v'max      = strltrim("`:di `dec' `=B[5,1]''")
                    local ++u
    }
    
    end
    
    dmed turn price if foreign==1, unit(cm %)
    ret li
    Sample return list display formats are below

    Code:
    macros:
               r(pricemax) : "12990"
               r(pricemin) : "3748"
               r(priceuqr) : "7140"
               r(pricemed) : "5759"
               r(pricelqr) : "4499"
               r(price_mr) : "5759% (range: 3748–12990%)"
             r(price_miqr) : "5759% (IQR: 4499–7140%)"
                r(turnmax) : "38"
                r(turnmin) : "32"
                r(turnuqr) : "36"
                r(turnmed) : "36"
                r(turnlqr) : "34"
                r(turn_mr) : "36cm (range: 32–38cm)"
              r(turn_miqr) : "36cm (IQR: 34–36cm)"
                    r(tot) : "22"

  • #2
    Closing the loop. I figured it out with an if statement. Seems inelegant but gets the job done. If folks want to use this for putdocx statements, feel free to use this code block. If there are obvious flaws or ways to improve the elegance, let me know and I am glad to learn.


    Code:
    // What this does: calculates a Mann-Whitney P for two groups and will
                    *  calculate median and IQR for all variables listed
                    *  by(dichotomous) var
                    * Will format p-values appropriately too.
                    
    *-------------------------------------------------------------------------------
    
    * Sample way to call this wrapper-Uncomment to check how it works
    *-------------------------------------------------------------------
        
        *! dmw.ado 1.1.0 GV 14nov2023 (updated pvalue display without leading zero or exponentials; added assertion for unit counts; adding value label automatically; code from Maarten Buis' post)
        *! dmw.ado 1.0.3 GV 11nov2023 (updated pvalue display without leading zero)
        *! dmw.ado 1.0.2 GV 7nov2023  (fixed addition of user defined texts after %)
        *! dmw.ado 1.0.1 GV 6nov2023  (fixed median not reached)
        *! dmw.ado 1.0.0 GV 6nov2023
        *! dmw.ado v1.0 GV 4nov2023
    
    
        cap program drop dmw
        return clear
        matrix drop _all
    program dmw, rclass
        version 18.0
        syntax varlist(numeric max=7), BY(varlist) [if] [unit(passthru)] [text(string)]
        marksample touse
        qui count if(`touse')
        return local tot = `r(N)'
        local endash = ustrunescape("\u2013")
        
    * Match number of units to number of contvars passed in varlist if there is a unit
    *---------------------------------------------------------------------------------
    cap assert `= wordcount("`unit'")' == `= wordcount("`varlist'")'
    if c(rc) {
        di as error "Number of variables and units don't match, Check spacing and totals"
    }
    
    * Grab value labels for all levels of each varlist to put as texts
    *-----------------------------------------------------------------
    
            local labname : value label `by'
            qui levelsof `by'
            foreach lev in `r(levels)' {
                   local `by'lab`lev' : label `labname' `lev'
    /*
                  gettoken `v'lab`lev' rest : `v'lab`lev', parse("(")
    */
                  }
                  di "`icc2lab1'"
    
        * Tokenize and parse units
    *-----------------------------
    local u = 3                    // All units start at tokenize 3 onwards: 1-"unit" 2 "(" and then 3 first unit "%"
    
            if "`unit'" != "" {
                tokenize `unit', parse("() ")
            }
            else {
                di "unit() is empty"
            }
    
    // Check loop1 for Mann-Whitney & p values
    *------------------------------
                                    
    foreach v of local varlist {
        qui ranksum `v', by(`by')                // Do Mann-Whitney for each variable
        local p = `r(p)'
        
            // Fix p-value formats
        *---------------------
        if `p'>=0.056 {
        local pvalue = "= " + substr(string(`p', "%3.2f"),2,.)
        }
        else if `p'>=0.044 & `p'<0.056 {
        local pvalue = "= " + substr(string(`p', "%5.3f"),2,.)
        }
        else if `p'>=0.001 & `p'<0.044  {
        local pvalue = "= " + substr(string(`p', "%4.3f"),2,.)
        }
        if `p' >=0.0001 & `p'<0.001  {
        local pvalue "< .001"
        }
        if `p' <0.0001 {
        local pvalue "< .0001"
        }
        local p`by'_`v' "`pvalue'"
    }
    
    
    // Collect all stats below for all variables around round to one decimal
    *-----------------------------------------------------------------------
      
        qui levelsof `by', local(mylev)
    
    * Loop 2 to collect all values for each var for each level into locals
    *---------------------------------------------------------------------
                                    
    foreach v of local varlist {                
            capture confirm float var `v', exact                //     Confirm these are numeric variables    
            if c(rc) == 0 {    
                    local dec "%3.1f"                            //     Decimal if it is a float variable
            }
            else {
                local dec "%8.0g"
            }
            *--------------------
           if "``u''" != "%"{                                    // Add space if not a percentage
            local k = " "
            }
            else {
                local k = ""
            }
            *---------------------
            qui tabstat `v', by(`by') statistic(p25 p50 p75 min max) save  // Get statistics for all
    
            qui mat list r(StatTotal)
            mat rename r(StatTotal) A
            local `v'lqr   = strltrim("`:di `dec' `=A[1,1]''" )         // Stats for each contvar---no byvar
            local `v'med   = strltrim("`:di `dec' `=A[2,1]''" )
            local `v'uqr   = strltrim("`:di `dec' `=A[3,1]''" )
            local `v'min   = strltrim("`:di `dec' `=A[4,1]''" )
            local `v'max   = strltrim("`:di `dec' `=A[5,1]''" )
            matrix drop A
            *--------------------------
        foreach lev of local mylev {                                     // Nesting 1 for each level within variable
            local j= `lev'+1                                            // Levels 0 is rStat1 and 1 is rStat2; hence `lev'+1
            qui mat list r(Stat`j')
            matrix rename r(Stat`j') B`j'
            qui mat list B`j'
                local `v'lqr`lev'  = strltrim("`:di `dec' `=B`j'[1,1]''")        // stats by contvar for each by level
                local `v'med`lev'  = strltrim("`:di `dec' `=B`j'[2,1]''")                    
                local `v'uqr`lev'  = strltrim("`:di `dec' `=B`j'[3,1]''")
                local `v'min`lev'  = strltrim("`:di `dec' `=B`j'[4,1]''")
                local `v'max`lev'  = strltrim("`:di `dec' `=B`j'[5,1]''")
            matrix drop B`j'
        }
        * Return local within outer loop, not outside the loops or inside inner loop
                return local `v'_miqr     `"``v'med'`k'``u'' (IQR: ``v'lqr'`endash'``v'uqr'`k'``u'')"'
                return local `v'_mr     `"``v'med'`k'``u'' (range: ``v'min'`endash'``v'max'`k'``u'')"'
                return local call`v'      "``v'med1'`k'``u'' vs. ``v'med0'`k'``u''; P `p`by'_`v''"
                return local p`v'          "P `p`by'_`v''"
                return local calltext`v'      "``v'med1'`k'``u'' vs. ``v'med0'`k'``u'' in `text'; P `p`by'_`v''"            // will pass custom text inside
                return local calllab`v'      "``v'med1'`k'``u'' vs. ``v'med0'`k'``u'' in ``by'lab0'; P `p`by'_`v''"        // will pass level label automatically
        local ++u            // Increment to gather next unit text by variable
    }
    
    end
    
    // Test the program
    dmw mpg weight, by(foreign) unit(% kgs.) text(custom state)
    ret li
    What this returns
    Code:
    macros:
          r(calllabweight) : "2180 kgs. vs. 3360 kgs. in Domestic; P < .0001"
         r(calltextweight) : "2180 kgs. vs. 3360 kgs. in custom state; P < .0001"
                r(pweight) : "P < .0001"
             r(callweight) : "2180 kgs. vs. 3360 kgs.; P < .0001"
              r(weight_mr) : "3190 kgs. (range: 1760–4840 kgs.)"
            r(weight_miqr) : "3190 kgs. (IQR: 2240–3600 kgs.)"
             r(calllabmpg) : "24.5% vs. 19% in Domestic; P = .002"
            r(calltextmpg) : "24.5% vs. 19% in custom state; P = .002"
                   r(pmpg) : "P = .002"
                r(callmpg) : "24.5% vs. 19%; P = .002"
                 r(mpg_mr) : "20% (range: 12–41%)"
               r(mpg_miqr) : "20% (IQR: 18–25%)"
                    r(tot) : "74"

    Comment

    Working...
    X