Announcement

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

  • dtable and Changing Font of All Elements

    Hi there -- I've searched the official Stata18 manual for the better part of an hour to find an answer, but haven't got it yet. Help Statalisters!

    I've just upgraded to Stata18, and I love the flexibility of the dtable command. I've worked out most customization for my current needs -- all except for text style changes. I had hoped setting the font for the entire created table would be intuitive, but that doesn't seem to be the case. While I've been successful in manipulating the table, column, and row headers in a single command (tedious, by the way!), the one part of the table that never gets modified by the text style options is the cells themselves. I can get column headers to look the way I want, individual row headers, etc -- but not the results themselves.

    Here's a sample code I'm working from:

    Code:
    clear all
    sysuse auto
    
    dtable price i.foreign, column( summary(, font(Garamond)) ) sample(, font(Garamond)) continuous(, font(Garamond)) factor(, font(Garamond)) titlestyles( font(Garamond) ) notestyles( font(Garamond) ) export("/Users/default/Downloads/Test.docx", as(docx) replace)
    Any help would be appreciated!

    Thanks,
    Kevin

  • #2
    There are many ways this can be done, but they all require using
    collect commands to modify the collection or define a collection
    style.

    I think my best suggestion is to use dtable to create your
    typical table, use dtable options and calls to collect
    style
    to get the styles you like, then save the collection's style
    to a file that you can then use in future projects.

    In the following, I use the above example but move the original
    font style changes to a call to collect style cell.
    This style edit changes the font family to Garamond for all cells
    in the table.
    Next I save the collection's style to a file.

    Code:
    . sysuse auto
    (1978 automobile data)
    
    . dtable price i.foreign
    
    --------------------------------
                      Summary      
    --------------------------------
    N                             74
    Price      6,165.257 (2,949.496)
    Car origin                      
      Domestic            52 (70.3%)
      Foreign             22 (29.7%)
    --------------------------------
    
    . collect style cell, font(Garamond)
    
    . collect style save style-my-dtable
    (style from DTable saved to file style-my-dtable.stjson)

    Aside:
    The style- prefix is not necessary, but it helps distinguish style files
    created by collect style save from collect labels files created by
    collect label save and collection files created by collect save;
    they all create JSON files and use the common extension .stjson.
    I can now specify this file in option style() in subsequent calls
    to dtable.

    If I really like this style, but don't want to have to keep specifying
    the style() option, I can copy this file to my PERSONAL
    folder, then set it as the default style for dtable with
    set dtable_style.

    Type sysdir to see where your PERSONAL folder resides on
    your machine, copy style-my-dtable.stjson to that folder, then in
    Stata type

    Code:
    set dtable_style my-dtable
    If you want to use this style in all future Stata sessions, add option
    permanently.

    Code:
    set dtable_style my-dtable, permanently
    In either case, dtable will search for style-my-dtable.stjson
    and use it if found, otherwise it will look for my-dtable.stjson.
    Failing that, you will get an error that Stata cannot find
    dtable's default style.
    Last edited by Jeff Pitblado (StataCorp); 03 May 2023, 19:53.

    Comment


    • #3
      Thank you, Jeff! This is INCREDIBLY helpful! I figured i needed to use the collect style command, but I didn't know how to use it for applying to cells. Deepest thanks!

      Comment


      • #4
        Hi again Jeff. Relatedly, how would I modify the n (%) decimal points when dtable suboption
        Code:
         sample(,place(seplabels))
        is invoked? Can that be modified using the [CODE] collect style cell [CODE] command as well? When the place(seplabels) option is in play, it seems to just hide the Sample row and create column labels based on that row's values. However, even if I modify the Sample row's value formats, the labels don't seem to change along with it.

        Any ideas?

        Thanks,
        Kevin
        Last edited by Kevin Blaine; 04 May 2023, 17:44.

        Comment


        • #5
          Found a work around -- instead of having a catch-all of 2 decimal places and then modifying anything else that I don't want to have that, I did the opposite and just called them all out in the initial dtable command:

          Code:
          nformat(%09.2gc rowpercent mean p50 p25 p75 sd)
          Though now I'm wondering if it's possible to save column layout as a permanent part of the style. For example, the "Total" column, which appears at the end of the table when invoking by( ), makes more sense in my work to be at the very beginning of the table. I can change it manually each time I make the table by doing the following:

          Code:
           collect layout (var) (foreign[.m 0 1]#result#_dtable_sample_dim)
          However, I'd love to make that change in column location permanent. Is this possible?

          Last edited by Kevin Blaine; 04 May 2023, 17:47.

          Comment


          • #6
            Let's start by changing the working example
            Code:
            . dtable mpg turn trunk i.rep78, ///
            >         by(foreign)
            
            ------------------------------------------------------------------
                                                   Car origin                 
                                     Domestic        Foreign         Total    
            ------------------------------------------------------------------
            N                         52 (70.3%)     22 (29.7%)    74 (100.0%)
            Mileage (mpg)         19.827 (4.743) 24.773 (6.611) 21.297 (5.786)
            Turn circle (ft.)     41.442 (3.968) 35.409 (1.501) 39.649 (4.399)
            Trunk space (cu. ft.) 14.750 (4.306) 11.409 (3.217) 13.757 (4.277)
            Repair record 1978                                                
              1                         2 (4.2%)       0 (0.0%)       2 (2.9%)
              2                        8 (16.7%)       0 (0.0%)      8 (11.6%)
              3                       27 (56.2%)      3 (14.3%)     30 (43.5%)
              4                        9 (18.8%)      9 (42.9%)     18 (26.1%)
              5                         2 (4.2%)      9 (42.9%)     11 (15.9%)
            ------------------------------------------------------------------
            First Kevin is using option sample(, place(seplabels)) to place
            the sample size information into the column header. Kevin then found
            option nformat() can be used to format those values, even though
            they are placed in the column header. Those values are identified as
            the results named frequency and percent.

            Kevin used %09.2gc, but the g format does not force 2 decimals,
            so I'm going to assume he meant to use %9.2fc. I dropped the leading
            0 because the f format will put the leading 0 for values less
            then 1 in absolute value.
            The default numeric format for percentages is documented as
            Code:
            nformat("%9.1fc" percent rawpercent fvpercent fvrawpercent)
            which accounts for sample percentages and the factor-variable level
            percentages. To format percentages with 2 decimal places, add option
            Code:
            nformat("%9.2f" percent rawpercent fvpercent fvrawpercent)
            in your call to dtable. I dropped the c because it is not
            necessary for percentages (my coding/documentation typo, mostly harmless).

            Kevin also wants 2 decimal places for other statistics, where the default
            is currently
            Code:
            nformat("%21.3fc")
            Similar to the percentages, add option
            Code:
            nformat("%21.2fc" mean sd p25 p50 p75)
            and make sure to specify the result names that you want formated with 2
            decimal places.

            I separated percentages and the other statistics because percentages
            have a limited range (value are 0.00...100.00) while the other statistic
            values can get quite large. I wanted to keep the 21 digit format width
            and fc.

            Honestly, Kevin's solution works just fine too, except rowpercent
            does not identify a built-in result name -- I assume he means
            percent. If you expect to see means greater than 99,999.99 and want
            your commas, then use format %21.2fc.
            In addition, if you are interested in adorning your sample frequencies
            in the column header, you can do this with option sformat(); such as
            Code:
            sformat("N=%s" frequency)
            Here is the table using the above options.
            Code:
            . dtable mpg turn trunk i.rep78, ///
            >         nformat(%9.2f percent rawpercent fvpercent fvrawpercent) ///
            >         nformat(%21.2fc mean p50 p25 p75 sd) ///
            >         sformat("N=%s" frequency) ///
            >         by(foreign) ///
            >         sample(, place(seplabels))
            
            ----------------------------------------------------------------
                                                  Car origin                
                                     Domestic      Foreign         Total    
                                  N=52 (70.27%) N=22 (29.73%) N=74 (100.00%)
            ----------------------------------------------------------------
            Mileage (mpg)          19.83 (4.74)  24.77 (6.61)   21.30 (5.79)
            Turn circle (ft.)      41.44 (3.97)  35.41 (1.50)   39.65 (4.40)
            Trunk space (cu. ft.)  14.75 (4.31)  11.41 (3.22)   13.76 (4.28)
            Repair record 1978                                              
              1                       2 (4.17%)     0 (0.00%)      2 (2.90%)
              2                      8 (16.67%)     0 (0.00%)     8 (11.59%)
              3                     27 (56.25%)    3 (14.29%)    30 (43.48%)
              4                      9 (18.75%)    9 (42.86%)    18 (26.09%)
              5                       2 (4.17%)    9 (42.86%)    11 (15.94%)
            ----------------------------------------------------------------
            Now, like my earlier post, you can save these style choices to a file
            and reference that file using option style() or set it as your default
            dtable style.

            Comment


            • #7
              Kevin wants dtable to put the by() totals first.

              Right now this is not possible without using collect after your
              call to dtable.

              I can't make any promises, but I can see a strong argument that we
              should add an option to dtable that makes this easy.

              In the mean time, I think Kevin's best bet is to use
              collect style autolevels to change the order of the by-levels
              instead of respecifying the layout.

              Here is a short program that will "fix" the Total position. The
              following program named fix_dtable_total requires that you
              identify the by-variable, verifies the specified variable is a dimension
              in the current collection, then changes the dimension's autolevels to put
              the "Total" level first. collect style autolevels ignores
              repeats, so you can just prepend the original by-variable's autolevels
              with the level you want to go first.

              Code:
              program fix_dtable_total
                  version 18
                  syntax varname [, first(string)]
                  quietly collect dims
                  local dims = s(dimnames)
                  if ! `:list varlist in dims' {
                      di as err "dimension `varlist' not found"
                      exit 111
                  }
                  if "`first'" == "" {
                      local first .m
                  }
                  quietly collect query autolevels `varlist'
                  collect style autolevels `varlist' `first' `s(levels)', clear
                  collect preview
              end
              By default dtable will use .m as a level of the by()
              variable to identify the total sample, even if the by-variable is a
              string variable. That is why option first() defaults to using
              .m.

              If you use option missing in option by(), and your
              by-variable has observations using .m, dtable will try to
              find an unused extended missing value. In this case, you need to tell
              fix_dtable_total what that value is in option first().
              You can find your by-variable's Total level using
              collect levels list.

              Code:
              . collect label list foreign
              
                Collection: DTable
                 Dimension: foreign
                     Label: Car origin
              Level labels:
                        .m  Total
                         0  Domestic
                         1  Foreign
              Here is fix_dtable_total in action using the collection from my
              previous post.

              Code:
              . fix_dtable_total foreign
              
              ----------------------------------------------------------------
                                                    Car origin
                                         Total        Domestic      Foreign
                                    N=74 (100.00%) N=52 (70.27%) N=22 (29.73%)
              ----------------------------------------------------------------
              Mileage (mpg)           21.30 (5.79)  19.83 (4.74)  24.77 (6.61)
              Turn circle (ft.)       39.65 (4.40)  41.44 (3.97)  35.41 (1.50)
              Trunk space (cu. ft.)   13.76 (4.28)  14.75 (4.31)  11.41 (3.22)
              Repair record 1978
                1                        2 (2.90%)     2 (4.17%)     0 (0.00%)
                2                       8 (11.59%)    8 (16.67%)     0 (0.00%)
                3                      30 (43.48%)   27 (56.25%)    3 (14.29%)
                4                      18 (26.09%)    9 (18.75%)    9 (42.86%)
                5                      11 (15.94%)     2 (4.17%)    9 (42.86%)
              ----------------------------------------------------------------

              Comment


              • #8
                Jeff, this is incredible. Wow. Thank you! Loving dtable so far, and no doubt I'll have some more formatting questions. Should I append them here or start a new thread?

                Comment


                • #9
                  You are welcome, and glad you like dtable.

                  I'm currently subscribed to this thread so I'll get an email if there are more posts here, but you should feel free to start a new thread for a new topic, even if it is still about dtable.

                  Comment


                  • #10
                    Thank you, Jeff! I do have another question, so I'll start a new thread now.

                    Comment


                    • #11
                      Jeff Pitblado (StataCorp) ,

                      This is great. I wanted to nest 3 continuous variables into a different tag and managed to do that. But when I do that tagging, rep78 in my table disappeared and in addition the N and % disappeared at the top. What can I do to fix this? I probably need to do this for several medical Table 1s.

                      Code:
                      sysuse auto, clear
                      
                      collect clear
                      
                      dtable mpg turn trunk i.rep78, ///
                               nformat(%9.2f percent rawpercent fvpercent fvrawpercent) ///
                               nformat(%21.2fc mean p50 p25 p75 sd) ///
                               sformat("N=%s" frequency) ///
                               by(foreign) ///
                               sample(, place(seplabels))
                      collect addtags vargrp[Testing], fortags(var[mpg turn trunk])
                      collect layout (vargrp#var) (foreign#result)
                      
                      collect preview
                      
                      --------------------------------------------------------------
                                                            Car origin              
                                                Domestic      Foreign       Total   
                      --------------------------------------------------------------
                      Testing                                                       
                        Mileage (mpg)         19.83 (4.74) 24.77 (6.61) 21.30 (5.79)
                        Turn circle (ft.)     41.44 (3.97) 35.41 (1.50) 39.65 (4.40)
                        Trunk space (cu. ft.) 14.75 (4.31) 11.41 (3.22) 13.76 (4.28)
                      Before tagging:
                      Code:
                      ----------------------------------------------------------------
                                                            Car origin                
                                               Domestic      Foreign         Total    
                                            N=52 (70.27%) N=22 (29.73%) N=74 (100.00%)
                      ----------------------------------------------------------------
                      Mileage (mpg)          19.83 (4.74)  24.77 (6.61)   21.30 (5.79)
                      Turn circle (ft.)      41.44 (3.97)  35.41 (1.50)   39.65 (4.40)
                      Trunk space (cu. ft.)  14.75 (4.31)  11.41 (3.22)   13.76 (4.28)
                      Repair record 1978                                              
                        1                       2 (4.17%)     0 (0.00%)      2 (2.90%)
                        2                      8 (16.67%)     0 (0.00%)     8 (11.59%)
                        3                     27 (56.25%)    3 (14.29%)    30 (43.48%)
                        4                      9 (18.75%)    9 (42.86%)    18 (26.09%)
                        5                       2 (4.17%)    9 (42.86%)    11 (15.94%)
                      ----------------------------------------------------------------

                      Comment


                      • #12
                        Your new layout does not include rep78 because none of the items with var[#.rep78] have your new dimension attached. You can (1) add a separate level of vargrp for the #.rep78 items, or (2) change your layout to include them.

                        (1) The following is a modified version of your example where I add a new level of vargrp for the #.rep78 items.
                        Code:
                        sysuse auto, clear
                        
                        collect clear
                        
                        dtable mpg turn trunk i.rep78, ///
                                 nformat(%9.2f percent rawpercent fvpercent fvrawpercent) ///
                                 nformat(%21.2fc mean p50 p25 p75 sd) ///
                                 sformat("N=%s" frequency) ///
                                 by(foreign) ///
                                 sample(, place(seplabels))
                        collect addtags vargrp[Testing], fortags(var[mpg turn trunk])
                        collect addtags vargrp[Factor], fortags(var[i.rep78])
                        collect layout (vargrp#var) (foreign#result)
                        
                        collect preview
                        Here is the resulting table
                        Code:
                        --------------------------------------------------------------
                                                              Car origin              
                                                  Domestic      Foreign       Total   
                        --------------------------------------------------------------
                        Testing                                                       
                          Mileage (mpg)         19.83 (4.74) 24.77 (6.61) 21.30 (5.79)
                          Turn circle (ft.)     41.44 (3.97) 35.41 (1.50) 39.65 (4.40)
                          Trunk space (cu. ft.) 14.75 (4.31) 11.41 (3.22) 13.76 (4.28)
                        Factor                                                        
                          Repair record 1978                                          
                            1                      2 (4.17%)    0 (0.00%)    2 (2.90%)
                            2                     8 (16.67%)    0 (0.00%)   8 (11.59%)
                            3                    27 (56.25%)   3 (14.29%)  30 (43.48%)
                            4                     9 (18.75%)   9 (42.86%)  18 (26.09%)
                            5                      2 (4.17%)   9 (42.86%)  11 (15.94%)
                        --------------------------------------------------------------

                        ​​​​​​​

                        (2) The following is a modified version of your example where I include var[i.rep78] in the layout.
                        Code:
                        sysuse auto, clear
                        
                        collect clear
                        
                        dtable mpg turn trunk i.rep78, ///
                                 nformat(%9.2f percent rawpercent fvpercent fvrawpercent) ///
                                 nformat(%21.2fc mean p50 p25 p75 sd) ///
                                 sformat("N=%s" frequency) ///
                                 by(foreign) ///
                                 sample(, place(seplabels))
                        collect addtags vargrp[Testing], fortags(var[mpg turn trunk])
                        collect layout (vargrp#var var[i.rep78]) (foreign#result)
                        
                        collect preview
                        Here is the resulting table
                        Code:
                        --------------------------------------------------------------
                                                              Car origin              
                                                  Domestic      Foreign       Total  
                        --------------------------------------------------------------
                        Testing                                                      
                          Mileage (mpg)         19.83 (4.74) 24.77 (6.61) 21.30 (5.79)
                          Turn circle (ft.)     41.44 (3.97) 35.41 (1.50) 39.65 (4.40)
                          Trunk space (cu. ft.) 14.75 (4.31) 11.41 (3.22) 13.76 (4.28)
                        Repair record 1978                                            
                          1                        2 (4.17%)    0 (0.00%)    2 (2.90%)
                          2                       8 (16.67%)    0 (0.00%)   8 (11.59%)
                          3                      27 (56.25%)   3 (14.29%)  30 (43.48%)
                          4                       9 (18.75%)   9 (42.86%)  18 (26.09%)
                          5                        2 (4.17%)   9 (42.86%)  11 (15.94%)
                        --------------------------------------------------------------

                        Comment


                        • #13
                          That's awesome. Now I will stick to making tables using Stata rather than resort to the R gtsummary package for the Table 1.

                          Comment


                          • #14
                            Originally posted by Jeff Pitblado (StataCorp) View Post
                            Your new layout does not include rep78 because none of the items with var[#.rep78] have your new dimension attached. You can (1) add a separate level of vargrp for the #.rep78 items, or (2) change your layout to include them.

                            (1) The following is a modified version of your example where I add a new level of vargrp for the #.rep78 items.
                            Code:
                            sysuse auto, clear
                            
                            collect clear
                            
                            dtable mpg turn trunk i.rep78, ///
                            nformat(%9.2f percent rawpercent fvpercent fvrawpercent) ///
                            nformat(%21.2fc mean p50 p25 p75 sd) ///
                            sformat("N=%s" frequency) ///
                            by(foreign) ///
                            sample(, place(seplabels))
                            collect addtags vargrp[Testing], fortags(var[mpg turn trunk])
                            collect addtags vargrp[Factor], fortags(var[i.rep78])
                            collect layout (vargrp#var) (foreign#result)
                            
                            collect preview
                            Here is the resulting table
                            Code:
                            --------------------------------------------------------------
                            Car origin
                            Domestic Foreign Total
                            --------------------------------------------------------------
                            Testing
                            Mileage (mpg) 19.83 (4.74) 24.77 (6.61) 21.30 (5.79)
                            Turn circle (ft.) 41.44 (3.97) 35.41 (1.50) 39.65 (4.40)
                            Trunk space (cu. ft.) 14.75 (4.31) 11.41 (3.22) 13.76 (4.28)
                            Factor
                            Repair record 1978
                            1 2 (4.17%) 0 (0.00%) 2 (2.90%)
                            2 8 (16.67%) 0 (0.00%) 8 (11.59%)
                            3 27 (56.25%) 3 (14.29%) 30 (43.48%)
                            4 9 (18.75%) 9 (42.86%) 18 (26.09%)
                            5 2 (4.17%) 9 (42.86%) 11 (15.94%)
                            --------------------------------------------------------------



                            (2) The following is a modified version of your example where I include var[i.rep78] in the layout.
                            Code:
                            sysuse auto, clear
                            
                            collect clear
                            
                            dtable mpg turn trunk i.rep78, ///
                            nformat(%9.2f percent rawpercent fvpercent fvrawpercent) ///
                            nformat(%21.2fc mean p50 p25 p75 sd) ///
                            sformat("N=%s" frequency) ///
                            by(foreign) ///
                            sample(, place(seplabels))
                            collect addtags vargrp[Testing], fortags(var[mpg turn trunk])
                            collect layout (vargrp#var var[i.rep78]) (foreign#result)
                            
                            collect preview
                            Here is the resulting table
                            Code:
                            --------------------------------------------------------------
                            Car origin
                            Domestic Foreign Total
                            --------------------------------------------------------------
                            Testing
                            Mileage (mpg) 19.83 (4.74) 24.77 (6.61) 21.30 (5.79)
                            Turn circle (ft.) 41.44 (3.97) 35.41 (1.50) 39.65 (4.40)
                            Trunk space (cu. ft.) 14.75 (4.31) 11.41 (3.22) 13.76 (4.28)
                            Repair record 1978
                            1 2 (4.17%) 0 (0.00%) 2 (2.90%)
                            2 8 (16.67%) 0 (0.00%) 8 (11.59%)
                            3 27 (56.25%) 3 (14.29%) 30 (43.48%)
                            4 9 (18.75%) 9 (42.86%) 18 (26.09%)
                            5 2 (4.17%) 9 (42.86%) 11 (15.94%)
                            --------------------------------------------------------------
                            @Jeff, I just noticed that the columns totals and percent display in the header disappeared when we did that. How do I fix that?

                            Comment


                            • #15
                              I left off the dimension with sample size labels that dtable created.
                              Here is what I should have posted.
                              Code:
                              sysuse auto, clear
                              
                              collect clear
                              
                              dtable mpg turn trunk i.rep78, ///
                              nformat(%9.2f percent rawpercent fvpercent fvrawpercent) ///
                              nformat(%21.2fc mean p50 p25 p75 sd) ///
                              sformat("N=%s" frequency) ///
                              by(foreign) ///
                              sample(, place(seplabels))
                              collect addtags vargrp[Testing], fortags(var[mpg turn trunk])
                              collect layout (vargrp#var var[i.rep78]) (foreign#result#_dtable_sample_dim)
                              
                              collect preview
                              Here is the resulting table.
                              Code:
                              ------------------------------------------------------------------
                                                                      Car origin                
                                                         Domestic      Foreign         Total    
                                                      N=52 (70.27%) N=22 (29.73%) N=74 (100.00%)
                              ------------------------------------------------------------------
                              Testing                                                           
                                Mileage (mpg)          19.83 (4.74)  24.77 (6.61)   21.30 (5.79)
                                Turn circle (ft.)      41.44 (3.97)  35.41 (1.50)   39.65 (4.40)
                                Trunk space (cu. ft.)  14.75 (4.31)  11.41 (3.22)   13.76 (4.28)
                              Repair record 1978                                                
                                1                         2 (4.17%)     0 (0.00%)      2 (2.90%)
                                2                        8 (16.67%)     0 (0.00%)     8 (11.59%)
                                3                       27 (56.25%)    3 (14.29%)    30 (43.48%)
                                4                        9 (18.75%)    9 (42.86%)    18 (26.09%)
                                5                         2 (4.17%)    9 (42.86%)    11 (15.94%)
                              ------------------------------------------------------------------

                              Comment

                              Working...
                              X