program define tabby
*!Tabulation with more control over format
/*
Date: 1/10/96
Last modification: 4/2/97, 16/6/97, 22/11/97, 20/4/98, 25/11/98, 18/2/99
Author: Wim van Putten, DDHCC, Rotterdam
Creates formatted table of one variable (or list of variables)
by another variable (optional). With possibility of suppression of
header and default display of descriptors of variables

Format:
 tabby varlist [if] [in],  options

See help.

 Other ado files required:      fns.ado

Note: bylab vervangen door blb
      byval vervangen door bvl

The by-var may also be a string variable!  Column labels will be abbreviated
to max 8 characters. With width() this may be even specified smaller.

3/4/01 Option test() added, which requests a Pearson Chisquare test, Kruskal Wallis test
or nptrend test or Spearman rank correlation test of association between
each row var and the column variable. Note in this part of the analysis missing values in
either the row or the column are not taken into account, even though they may be listed
in the tabby table!

11/5/01 Width convention has been changed. Specifies now the minimum width of columns.
Each column separately get a wider width if necessary.

*/

version 5.0
quietly {
local varlist "req ex min(1) "
#delimit ;
local options "BY(string) Label(string) Bymis Mis Width(int 10)
        Head Total Col Row Perc Dec(int 0) Start(int 12)
        noBYTot noZero MAX(int 100) TEst(string) ";
#delimit cr
local weight "fweight"
local if "opt"
local in "opt"
parse `"`0'"'
if "`weight'"~="" {local weight [`weight' `exp'] }
preserve

if `"`if'`in'"'~="" { keep `if' `in'}
if _N==0 {
        noisily display `"`if' `in' : no observations"'
        exit
}
tempvar _blb _vlab _val NN  NNN SNN CAT  rownr NR RTOT CTOT


parse "`by'",parse(" ,")
local by "`1'"               /* Only first string counts as by-variable */
local nby=1
if "`by'"~="" {
        unabbrev `by'
        local byname : variable label `by'
        local bytype : type  `by'
        if index("`bytype'","str")>0 {
                replace `by'=trim(`by')
                compress `by'
        }
        if "`byname'"=="" { local byname "`by'" }
        if "`bymis'"=="" {     /* Exclude observations missing on by-variable
                                unless the missing are included as category */
           if index("`bytype'","str")>0 { drop if `by'=="" }
           else { drop if `by'==. }
           if _N==0 {
                noisily display "no observations with `by' non-missing"
                exit
           }
        }
        tempvar colnr
        sort `by'
        quietly by `by' : gen int `NNN'=1 if _n==1
        gen int `colnr'=sum(`NNN')
        local nby=`colnr'[_N]
        local blb : value label `by'
        if "`blb'"~="" {
           capture label list `blb'
           if _rc>0 {   /* value label not defined */
                local blb
           }
        }
        if index("`bytype'","str")>0 {
         local w=max(`width',2)
         gen str`w' `_blb' = `by'
        }
        else {
         if "`blb'"~="" {  decode `by' ,gen(`_blb') }
         else           {  gen str8 `_blb'=string(`by') }
         replace `_blb'=string(`by') if `_blb'==""
        }
        local byby "by(`by')"
}

if index("`test'","chi")>0 {local test Chi2}
else if index("`test'","tr")>0 {local test trendtest }
else if index("`test'","kw")>0 {local test K-W }
else if index("`test'","sp")>0 {local test Spearman}
else if index("`test'","rh")>0 {local test Spearman }
else if index("`test'","fish")>0 {local test exact}
else if index("`test'","exac")>0 {local test exact}

if "`col'"~="" |("`perc'"=="perc"&"`by'"~="") { local perc col }
else if "`row'"~=""&"`by'"~=""                { local perc row }
if "`perc'"=="row"&`nby'<=1 {
         local perc
         local row
}
if `start'<12 {local start 12}

*Determine max column width of all numeric strings to be displayed.
*Separately for body of table and the total-column!
*11/5/2001: separately for each column of the table now!
if "`perc'`row'`col'"~="" {
 if `dec'>0  { local pwidth =4+`dec' }
 else        { local pwidth =3       }
}
else { local pwidth=0 }
egen `NN'=count(1),`byby'
local sumwid=0

if `nby'>1&"`row'"!="" {
  local j=1
  while `j'<=`nby' {
    local width`j'=max(`width',`pwidth')
    local sumwid=`sumwid'+`width`j''
    local j=`j'+1
  }
}
else if `nby'>1&"`perc'`col'"!="" {
  local j=1
  while `j'<=`nby' {
    quietly su `NN' if `colnr'==`j'
    local l=r(max)
    local width`j'=max(`width',`pwidth',length("`l'"))
    local sumwid=`sumwid'+`width`j''
    local j=`j'+1
  }
}
else if `nby'>1 {
  local j=1
  while `j'<=`nby' {
    quietly su `NN' if `colnr'==`j'
    local l=r(max)
    local width`j'=max(`width',length("`l'"))
    local sumwid=`sumwid'+`width`j''
    local j=`j'+1
  }
}
else {
  quietly su `NN'
  local l =r(max)
  local width1=max(`width',length("`l'"))
  if "`perc'`col'"~=""  {local width1=max(`width1',`pwidth')}
  local sumwid=`sumwid'+`width1'
}
drop `NN'

*Create labels for head of Table, if requested
*Shorten the label if  length > width .      Modification 25/11/98.
if "`by'"~="" {
 sort `NNN' `colnr'
 local j=1
 while `j'<=`nby' {
        local blb`j'= substr(`_blb'[`j'],1,`width`j'')
        local bvl`j'=`by'[`j']
        local j=`j'+1
 }
 drop  `NNN'
}

local l=_N
local wtot=max(5,`width',1+length("`l'"))  /* width for total column */

keep `varlist' `by'  `colnr'

local ntot=_N
if "`total'"~=""&"`mis'"~=""&"`by'"~="" {
  egen `NN'=count(1),`byby'
  sort `by'
  quietly by `by' : gen int `NNN'=1 if _n==1
  sort `NNN' `colnr'
  local j=1
  while `j'<=`nby' {
        if "`perc'"=="row" {
           local b=100*`NN'[`j']/`ntot'
           fns a `b' ,w(`width`j'') d(`dec')
           local n_`j'  $a
        }
        else { local n_`j'=max(0,`NN'[`j'])  }
        local j=`j'+1
  }
  drop `NN' `NNN'
}

*Adapt the start column of the body of the table to the max width required
* for variables in the varlist
eformat `varlist'" ,val lab
parse "`varlist'" ,parse(" ")
while "`1'"~="" {
 local w: char `1'[width]
 local start =max(`start',`w'+4)
 macro shift
}

local s1 =`start'-1
local s2 =`start'
local s3 =`start'+1

local Ppos=`start'+`sumwid'

} /* end quietly */
if "`head'"~="" &(`nby'>1|"`by'"~="") {
        display _col(`s2') "|  `byname' "
        display _col(`s2') "|"   _continu
        local j=1
        while `j'<=`nby' {
                local skip=`width`j''-length("`blb`j''")
                display _skip(`skip') "`blb`j'' " _contin
                local j= `j'+1
        }
        if `nby'>1&("`bytot'"==""|"`perc'"=="row") {
                display "|" _cont
                local skip=max(0,`wtot'-length("Total"))
                local last=max(5,`wtot')
                display  _skip(`skip') "Total"
        }
        else {
                display " "
                local last 1
        }
        local j=`sumwid'+`nby'

        if "`perc'"=="row" &`nby'>1{
          if `j'>18 {
                local p1=`start'+4+int((`j'-18)/2)
                local pt [row percentages]
                local p2=max(`p1'+18,`start'+`j'+`wtot'-1)
                local ptot [#]
          }
          else {
                local p1=`start'+3+int((`j'-7)/2)
                local pt [row %]
                local p2=max(`p1'+8,`start'+`j'+`wtot'-1)
                local ptot [#]
          }
        }
        else if "`perc'"=="col" {
          if `j'>21 {
                local p1=`start'+3+int((`j'-21)/2)
                local pt [column percentages]
                local p2=max(`p1'+22,`start'+`j'+6)
                local ptot
          }
          else {
                local p1=`start'+3+int((`j'-10)/2)
                local pt [column %]
                local p2=max(`p1'+11,`start'+`j'+6)
                local ptot
          }
        }
        if "`bytot'"~=""&"`perc'"~="row" { local ptot }
        if "`perc'"~="" { display _col(`p1') "`pt'" _col(`p2') "`ptot'" }
        display _dup(`s1') "-" "+"   _continue
        if "`bytot'"==""|"`perc'"=="row" {
                display _dup(`j') "-" "+" _dup(`last') "-"
        }
        else{
                local j1 =`j'-1
                display _dup(`j1') "-"
        }
}
else if "`head'"=="" &`nby'>1&"`perc'"!="$preperc"  {
        local j=`sumwid'+`nby'

        if "`perc'"=="row" &`nby'>1{
          if `j'>18 {
                local p1=`start'+4+int((`j'-18)/2)
                local pt [row percentages]
          }
          else {
                local p1=`start'+3+int((`j'-7)/2)
                local pt [row %]
          }
        }
        else if "`perc'"=="col" {
          if `j'>21 {
                local p1=`start'+3+int((`j'-21)/2)
                local pt [column percentages]
          }
          else {
                local p1=`start'+3+int((`j'-10)/2)
                local pt [column %]
          }
        }
        else  {
          if `j'>18 {
                local p1=`start'+4+int((`j'-18)/2)
                local pt [frequencies]
          }
          else {
                local p1=`start'+3+int((`j'-7)/2)
                local pt [freq.]
          }
        }
        display _col(`p1') "`pt'"
}
else if `nby'<=1&"`by'"=="" & "`perc'"=="col" {
        local p1=`start'+`width1'-5
        display _col(`p1') "[column %]"
}
else if `nby'<=1&"`by'"==""  & "`perc'"=="perc" &"`head'"!="" {
        local p1=`start'+`width1' - 3
        display _col(`p1') "Number     [%]"
}
global preperc `perc'
if "`total'"~=""&"`mis'"~="" {
  if ("`perc'"~="row"&"`by'"~="") {
        display " Total [#]" _col(`s2') "|" _continue
        local j=1
        while `j'<=`nby' {
                local skip=`width`j''-length("`n_`j''")
                display _skip(`skip') "`n_`j'' " _contin
                local j= `j'+1
        }
        if ("`bytot'"==""|"`perc'"=="row") &`nby'>1{
                display "|" _cont
                local skip=`wtot'-length("`ntot'")
                display  _skip(`skip') "`ntot'"
        }
        else {  display " " }
  }
  else  if "`perc'"=="row"&`nby'>1 {
        display " Total [%]" _col(`s2') "|" _continue
        local j=1
        while `j'<=`nby' {
                local skip=`width`j''-length("`n_`j''")
                display _skip(`skip') "`n_`j'' " _contin
                local j= `j'+1
        }
        display "|" _cont
        local skip=`wtot'-length("`ntot'")
        display  _skip(`skip') "`ntot'"
  }
  else if "`by'"=="" {
        display " Total [#]" _col(`s3') "|" _continue
        local skip=1+`width1'-length("`ntot'")
        display  _skip(`skip') "`ntot'"  _cont
        if "`perc'"=="perc" {   display  "    100%" }
        else {display " " }
  }
  display " "
}
parse "`varlist'" ,parse(" ")
while "`1'"~="" {
 quietly {
  eformat `1' ,val lab
  capture drop `NN'
  capture drop `NNN'
  capture drop `_vlab'
  capture drop `rownr'
  capture drop `NR'
  sort `1'
  quietly by `1' : gen int `NNN'=1 if _n==1
  gen int `NR' =`NNN'
  gen int `rownr'=sum(`NNN')
  local nrow=`rownr'[_N]  /* inclusive any missing value category ! */
  global varlab : variable label `1'
  if "$varlab"=="" { global varlab "`1'"}
  if `nrow'<=`max' {
   local type : type `1'
   local form : format `1'
   local vallab : value label `1'
   local type1 : type `1'
   if index("`type'","str")~=0  {
        local form
        gen `type1' `_vlab'=`1'
        compress `_vlab'
        local type : type `_vlab'
   }
   else {
      if "`vallab'"~="" {
        capture label list `vallab'
        if _rc>0 {      /* value label not defined */
                local vallab
        }
      }
      if "`vallab'"~="" {           /* variable with existing value label */
        decode `1' ,gen(`_vlab')
        replace `_vlab'=string(`1') if `_vlab'==""
        replace `_vlab'="." if `_vlab'==""
        local form
        local type : type `_vlab'
      }
      else  {
        gen `type' `_vlab' = `1'    /* all other variables */
      }
   }
  *Statistic for overall total
   if index("`type1'","str")==0|"`mis'"~="" { tab `1',`mis'}
   else  { tab `1' if "`1'"~=""}
   local ntot=_result(1)
   local nval=_result(2)

  *Statistics for row-total
   egen `NN'=count(1),by(`1')
   sort `1'

   local plus1
   if "`mis'"==""&index("`type1'","str")>0 {
    tempvar len1
    gen byte `len1'=length(`1')
    quietly su `len1'
    if _result(5)==0 { local plus1 +1}
   } /* plus1 will be used later to ignore-not display missing categories */

   capture drop `NNN'
   quietly by `1' : gen int `NNN'=1 if _n==1
   sort `NNN' `rownr'
   local i=1
   while `i'<=`nrow' {
        if index("`type'","str")~=0  {
          /* Take care of possible double quotes in vlab`i'!! */
                local vlab`i' `_vlab'[`i']
        }
        else {
                local vlab`i'= `_vlab'[`i']
        }
        local val`i'=`1'[`i']
        if "`perc'"~="col" {
                local n`i'=`NN'[`i']}
        else {
                local b=100*`NN'[`i']/`ntot'
                fns a `b' ,w(`wtot') d(`dec')
                local n`i' $a }
        local i=`i'+1 }
   drop  `NN' `NNN'

  *Statistics for column totals
   if ("`perc'"=="col"|("`total'"~=""&"`mis'"=="")|"`test'"=="Chi2")&"`by'"~="" {
    if "`mis'"~="" {
     egen `NN'=count(1),`byby'
    }
    else if index("`type1'","str")==0  {
     egen `NN'=count(1) if `1'~=. ,`byby'
    }
    else   { /* empty strings of rowvar to be considered as missing and not counted ! */
     egen `NN'=sum(`len1'>0) ,`byby'
    }
    sort `by' `1'
    quietly by `by' : gen int `NNN'=1 if _n==1
    sort `NNN' `colnr'
    local j=1
    while `j'<=`nby' {
        if "`perc'"=="row" {
           local b=100*`NN'[`j']/`ntot'
           fns a `b' ,w(`width`j'') d(`dec')
           local n_`j'  $a
        }
        else { local n_`j'=max(0,`NN'[`j']) }
        local j=`j'+1
    }
    if "`test'"=="Chi2"&"`by'"!="" {
      gen int `CTOT' =`NN'
      if index("`bytype'","str")>0 { replace `CTOT'=. if `by'=="" }  /* added 8/10/01 */
      else                         { replace `CTOT'=. if `by'==.  }  /* to exclude missing obs for chi2 p_value*/
      if index("`type1'","str")>0  { replace `CTOT'=. if `1'==""  }
      else                         { replace `CTOT'=. if `1'==.   }

    } /* column totals needed in case of chi2 test; but restricted to non-missing obs  */
    drop `NN' `NNN'
   }

  *Statistics for cells
   if "`by'"~="" {
    egen `NN'=count(1),by(`1' `by' )
    local pval
    if "`test'"=="Chi2" {
      egen `RTOT'=count(1) if `CTOT'!=.&`CTOT'!=0 ,by(`1')
      qui tab `1' `by', chi
      local rr=r(r)
      local rc=r(c)
      local rn=r(N)
      local rp=r(p)
      qui count if `CTOT'*`RTOT'>0&`CTOT'*`RTOT'!=.&(`CTOT'<3*`rr'|`RTOT'<3*`rc')
      if `rr'>=2&`rc'>=2&4*`rr'*`rc'<=`rn'&r(N)==0 {local pval =`rp'}
       /*changed  30/5/2001
      A p-value for chi2 test will NOT be calculated if the mean number of counts per cell is
      less than 4 or if if the mean number of counts per cell per column (row) is less than
      3 derived form column (row) total and number of rows (columns) */
      drop `CTOT' `RTOT'
    }
    else if "`test'"=="Spearman" {
        cap qui spearman `1' `by'  /* 25/11/02 capture added for cases where Spearman cannot be calculated
                                               also below */
        local pval =r(p)
    }
    else if "`test'"=="K-W" {
        cap qui kwallis `1' ,by(`by')
        local chi2=r(chi2_adj)            /* 17/12/02 adapted WvP */
        if `chi2'==. { local chi2=r(chi2)}
        local pval=chi2tail(r(df),`chi2')
    }
    else if "`test'"=="trendtest" {
        cap qui nptrend `1' ,by(`by')
        local pval =r(p)
    }
    else if "`test'"=="exact" {
        cap qui tab `1' `by' , exact
        local pval = r(p_exact)
    }
    sort `1' `by'
    quietly by `1' `by' : gen int `NNN'=1 if _n==1
    sort `NNN' `rownr' `colnr'
    local i=1
    local k=1
    while `i'<=`nrow' {
     local j=1
     while `j'<=`nby' {
        local r=`rownr'[`k']
        local c=`colnr'[`k']
        if `r'==`i' & `c'==`j' {
         if "`perc'"=="col" {
           local b=100*`NN'[`k']/`n_`j''
           fns a `b' ,w(`width`j'') d(`dec')
           local n`i'_`j'  $a
           if "`zero'"!=""&`b'==0 { local n`i'_`j' . }
         }
         else if "`perc'"=="row" {
           local b=100*`NN'[`k']/`n`i''
           fns a `b' ,w(`width`j'') d(`dec')
           local n`i'_`j'  $a
           if "`zero'"!=""&`b'==0 { local n`i'_`j' . }
         }
         else {
           local n`i'_`j'=`NN'[`k']
           if "`zero'"!=""&`NN'[`k']==0 { local n`i'_`j' . }
         }
         local j=`j'+1
         local k=`k'+1
        }
        else {
         local n`i'_`j'=0
         if "`zero'"!="" { local n`i'_`j' . }
         local j=`j'+1
        }
     }
     local i=`i'+1
    }
    drop `NN' `NNN'
   } /* if by */
  }
 } /* end quietly */
 if "`pval'"!=""&`nrow'<=`max' {
   display "$varlab" _col(`Ppos') "(P=" %5.3f `pval' ", `test')"
  }
 else { display "$varlab" }
 if `nrow'>`max' {
         display " Too many (`nrow'>`max') different values"
 }
 else {
  sort `NR' `1'
  local i=1 `plus1'
  while `i'<=`nval'`plus1' {
        display _col(3) `form' `_vlab'[`i'] _col(`s2')   _continu
        if "`by'"~="" {
          display     "|"          _continu
          local j=1
          while `j'<=`nby' {
                local skip=`width`j''-length("`n`i'_`j''")
                display _skip(`skip') "`n`i'_`j'' " _contin
                local j= `j'+1
          }
          if `nby'>1&("`bytot'"==""|"`perc'"=="row") {
            display "|" _cont
            local skip=`wtot'-length("`n`i''")
            display  _skip(`skip') "`n`i''"
          }
          else {  display " " }
        }
        else {
          display  _skip(1) "|" _cont
          local skip=1+`width1'-length("`n`i''")
          display  _skip(`skip') "`n`i''" _con
          if "`perc'"=="perc" {
                local b=100*`n`i''/`ntot'
                fns a `b' ,w(`wtot') d(`dec')
                local skip=max(1,7-length("$a"))
                display  _skip(`skip') "$a" "%"
          }
          else {display " "}
        }
        local i= `i'+1
  }
  if "`total'"~=""&"`mis'"=="" {
        if "`perc'"=="col" {
                display _col(2) "Total [#]" _col(`s2')   _continu }
        else  {
                display _col(2) "Total " _col(`s2')   _continu }
        if "`by'"~="" {
          display     "|"          _continu
          local j=1
          while `j'<=`nby' {
                local skip=`width`j''-length("`n_`j''")
                display _skip(`skip') "`n_`j'' " _contin
                local j= `j'+1
          }
          if `nby'>1&("`bytot'"==""|"`perc'"=="row") {
                display "|" _cont
                local skip=`wtot'-length("`ntot'")
                display  _skip(`skip') "`ntot'"
          }
          else {  display " " }
        }
        else {
          display  _skip(1) "|" _cont
          local skip=1+`width1'-length("`ntot'")
          display  _skip(`skip') "`ntot'"  _cont
          if "`perc'"=="perc" { display  "    100%" }
          else {display "" }
        }
  }
 }
 display " "
 macro shift
} /* end while loop over varlist */
end

