# Is there an R equivalent of SAS PROC FREQ?

Does anyone know of an R equivalent to SAS PROC FREQ?

I am trying to generate summary descriptive statistics for multiple variables at once.

• Why was this question closed? It relates to data visualization and generated several worthwhile responses. – z0lo Feb 28 '13 at 14:26

I use table and prop.table, but CrossTable in the gmodels package might give you results even closer to SAS. See this link.

Also, to generate "descriptive statistics for multiple variables at once," you would use the summary function; e.g., summary(mydata).

Summarising data in base R is just a headache. This is one of the areas where SAS works quite well. For R, I recommend the plyr package.

In SAS:

/* tabulate by a and b, with summary stats for x and y in each cell */
proc summary data=dat nway;
class a b;
var x y;
output out=smry mean(x)=xmean mean(y)=ymean var(y)=yvar;
run;


with plyr:

smry <- ddply(dat, .(a, b), summarise, xmean=mean(x), ymean=mean(y), yvar=var(y))


I don't use SAS; so I can't comment on whether the following replicate SAS PROC FREQ, but these are two quick strategies for describing variables in a data.frame that I often use:

• describe in Hmisc provides a useful summary of variables including numeric and non-numeric data
• describe in psych provides descriptive statistics for numeric data

### R Example

> library(MASS) # provides dataset called "survey"
> library(Hmisc) # Hmisc describe
> library(psych) # psych describe


The following is the output of Hmisc describe:

> Hmisc::describe(survey)
survey

12  Variables      237  Observations
----------------------------------------------------------------------------------------------------------------------
Sex
n missing  unique
236       1       2

Female (118, 50%), Male (118, 50%)
----------------------------------------------------------------------------------------------------------------------
Wr.Hnd
n missing  unique    Mean     .05     .10     .25     .50     .75     .90     .95
236       1      60   18.67   16.00   16.50   17.50   18.50   19.80   21.15   22.05

lowest : 13.0 14.0 15.0 15.4 15.5, highest: 22.5 22.8 23.0 23.1 23.2
----------------------------------------------------------------------------------------------------------------------
NW.Hnd
n missing  unique    Mean     .05     .10     .25     .50     .75     .90     .95
236       1      68   18.58   15.50   16.30   17.50   18.50   19.72   21.00   22.22

lowest : 12.5 13.0 13.3 13.5 15.0, highest: 22.7 23.0 23.2 23.3 23.5
----------------------------------------------------------------------------------------------------------------------
[ABBREVIATED OUTPUT]


Then below is the output of psych describe for the numeric variables:

> psych::describe(survey[,sapply(survey, class) %in% c("numeric", "integer") ])
var   n   mean    sd median trimmed   mad    min   max range  skew kurtosis   se
Wr.Hnd   1 236  18.67  1.88  18.50   18.61  1.48  13.00  23.2 10.20  0.18     0.36 0.12
NW.Hnd   2 236  18.58  1.97  18.50   18.55  1.63  12.50  23.5 11.00  0.02     0.51 0.13
Pulse    3 192  74.15 11.69  72.50   74.02 11.12  35.00 104.0 69.00 -0.02     0.41 0.84
Height   4 209 172.38  9.85 171.00  172.19 10.08 150.00 200.0 50.00  0.22    -0.39 0.68
Age      5 237  20.37  6.47  18.58   18.99  1.61  16.75  73.0 56.25  5.16    34.53 0.42


I use the codebook function from {EPICALC} which gives summary statistics for a numeric variable and a frequency table with level labels and codes for factors. http://cran.r-project.org/doc/contrib/Epicalc_Book.pdf (see p.50) Moreover, this is very useful because it provides sd for quantitative variables.

Enjoy !

• +1 (from earlier). I really like the way codebook() lays this out. 1 issue is that nas are dropped, which you may want included in your output. 1 way to deal w/ this (at least w/ factors) is to use ?recode.is.na 1st (eg, to "missing"); for numeric variables, you can create a new variable immediately to the left of the column w/ a logical value based on is.na(), then run codebook(). It is a bit of a kluge, though. – gung Dec 4 '12 at 15:35

You can check out my summarytools package (CRAN link) which includes a codebook-like function, with markdown and html formatting options.

install.packages("summarytools")
library(summarytools)
dfSummary(CO2, style = "grid", plain.ascii = TRUE)


## CO2

+------------+---------------+-------------------------------------+--------------------+-----------+
| Variable   | Properties    | Stats / Values                      | Freqs, % Valid     | N Valid   |
+============+===============+=====================================+====================+===========+
| Plant      | type:integer  | 1. Qn1                              | 1: 7 (8.3%)        | 84/84     |
|            | class:ordered | 2. Qn2                              | 2: 7 (8.3%)        | (100.0%)  |
|            | + factor      | 3. Qn3                              | 3: 7 (8.3%)        |           |
|            |               | 4. Qc1                              | 4: 7 (8.3%)        |           |
|            |               | 5. Qc3                              | 5: 7 (8.3%)        |           |
|            |               | 6. Qc2                              | 6: 7 (8.3%)        |           |
|            |               | 7. Mn3                              | 7: 7 (8.3%)        |           |
|            |               | 8. Mn2                              | 8: 7 (8.3%)        |           |
|            |               | 9. Mn1                              | 9: 7 (8.3%)        |           |
|            |               | 10. Mc2                             | 10: 7 (8.3%)       |           |
|            |               | ... 2 other levels                  | others: 14 (16.7%) |           |
+------------+---------------+-------------------------------------+--------------------+-----------+
| Type       | type:integer  | 1. Quebec                           | 1: 42 (50%)        | 84/84     |
|            | class:factor  | 2. Mississippi                      | 2: 42 (50%)        | (100.0%)  |
+------------+---------------+-------------------------------------+--------------------+-----------+
| Treatment  | type:integer  | 1. nonchilled                       | 1: 42 (50%)        | 84/84     |
|            | class:factor  | 2. chilled                          | 2: 42 (50%)        | (100.0%)  |
+------------+---------------+-------------------------------------+--------------------+-----------+
| conc       | type:double   | mean (sd) = 435 (295.92)            | 95: 12 (14.3%)     | 84/84     |
|            | class:numeric | min < med < max = 95 < 350 < 1000   | 175: 12 (14.3%)    | (100.0%)  |
|            |               | IQR (CV) = 500 (0.68)               | 250: 12 (14.3%)    |           |
|            |               |                                     | 350: 12 (14.3%)    |           |
|            |               |                                     | 500: 12 (14.3%)    |           |
|            |               |                                     | 675: 12 (14.3%)    |           |
|            |               |                                     | 1000: 12 (14.3%)   |           |
+------------+---------------+-------------------------------------+--------------------+-----------+
| uptake     | type:double   | mean (sd) = 27.21 (10.81)           | 76 distinct values | 84/84     |
|            | class:numeric | min < med < max = 7.7 < 28.3 < 45.5 |                    | (100.0%)  |
|            |               | IQR (CV) = 19.23 (0.4)              |                    |           |
+------------+---------------+-------------------------------------+--------------------+-----------+


EDIT

In newer versions of summarytools, the freq() function (which produces straightforward frequency tables, more to-the-point as regards to the original question) accepts data frames as well as single variables. For cross-tabulations (which proc freq also does), see the ctable() function.

freq(CO2)


### Frequencies

CO2$Plant Type: Ordered Factor  Freq % Valid % Valid Cum % Total % Total Cum Qn1 7 8.33 8.33 8.33 8.33 Qn2 7 8.33 16.67 8.33 16.67 Qn3 7 8.33 25.00 8.33 25.00 Qc1 7 8.33 33.33 8.33 33.33 Qc3 7 8.33 41.67 8.33 41.67 Qc2 7 8.33 50.00 8.33 50.00 Mn3 7 8.33 58.33 8.33 58.33 Mn2 7 8.33 66.67 8.33 66.67 Mn1 7 8.33 75.00 8.33 75.00 Mc2 7 8.33 83.33 8.33 83.33 Mc3 7 8.33 91.67 8.33 91.67 Mc1 7 8.33 100.00 8.33 100.00 <NA> 0 0.00 100.00 Total 84 100.00 100.00 100.00 100.00  CO2$Type

Type: Factor

                Freq   % Valid    % Valid Cum   % Total    % Total Cum
Quebec     42     50.00          50.00     50.00          50.00
Mississippi     42     50.00         100.00     50.00         100.00
<NA>      0                               0.00         100.00
Total     84    100.00         100.00    100.00         100.00

CO2\$Treatment

Type: Factor

               Freq   % Valid    % Valid Cum   % Total    % Total Cum
nonchilled     42     50.00          50.00     50.00          50.00
chilled     42     50.00         100.00     50.00         100.00
<NA>      0                               0.00         100.00
Total     84    100.00         100.00    100.00         100.00


Thanks for all the suggestions everyone. I ended up using either table or Rcmdr's numSummary function plus apply:

apply(dataframe[,c('need_rbcs','need_platelets','need_ffp')],2,table)


This works pretty well and is not too inconvenient. However I will definitely give some of these other solutions a try!