Computing in R: Frequency Tables -- Grouped Values
Return to Topics page
Please note that at the end of this page there is a listing of the
R commands that were used to generate the output shown in
the various figures on this page.
This page presents R commands related to building
and interpreting frequency tables for grouped values.
To do this we need some example data.
We will use the values given in Table 1.
Because this data has so many different values, it would not make sense to
look at it as discrete values. Rather,
we want to group these values into bins or buckets.
In the preceding discussion of grouped data
page we developed a table such as Table 2
to give the appropriate values for this data.
Table 2 has been created within this web page, based on the values
displayed in Table 1.
Our task is to find the commands that we can use in R to generate this
To start, we need to generate the data in R and
find the low and high values in that data.
Figure 1 shows the gnrnd4() statement used to
generate the same data in R,
followed by the L1
statement so that we can be sure that we generated the right data.
A quick comparison shows that the values are identical.
Figure 1 also demonstrates the use of the
tail() functions as a shorter way to
verify the data.
follow that with the summary(L1) statement.
Figure 1 shows the related output, including a display of
the minimum and maximum values.
We need those values so that we can make a decision about the
places where we want to break the range of values, from a
minimum of 35.8 to a maximum of 102.3.
In order to have some nice "endpoints" to our intervals we
can start them at 30, end them at 110, and have an
interval width of 10.
There is no real need to put those values into variables,
but we can do just that. The first 3 statements
in Figure 2 make such assignments.
The seventh line in Figure 2,
x_breaks <- seq( low_val, high_val, step_val)
creates a new variable and stores in that variable the sequence
of values 30, 40, 50, 60, 70, 80, 90, 100, and 110.
We see those values displayed as a result of the command
As long as we are using the seq() function, we might as well set up
a sequence of the midpoint values.
Figure 3 shows the command that we use, namely,
x_mid <- seq( low_val+step_val/2, high_val-step_val/2, step_val), to
create such a sequence, and that same figure
shows the values stored in x_mid.
R has a command, cut(), that expects us to give it a
collection of values and the break points to use with those
values. We formulate the command as
x <-cut(L1, breaks=x_breaks), as shown in Figure 4.
The result of the cut() command is a collection
of intervals, all conforming to the
break points that we gave cut() with the specific
interval values representing the interval into
which the corresponding value in the data, in our case L1,
falls. Consider the output shown in Figure 4.
The first value in L1, the first value in Table 1,
is 75.2. That value falls into the (70,80] interval.
Therefore, the first value out of cut() is (70,80].
The second value in L1 is 66.0; that value falls into the
Therefore, the second value out of cut() is (60,70].
The third value in L1 is 74.5; that value falls into the
Therefore, the third value out of cut() is (70,80].
The fourth value in L1 is 55.5; that value falls into the
Therefore, the fourth value out of cut() is (50,60].
And so on through the rest of the values in L1.
The interesting thing here is that we start with L1 as a collection of
and we end up with x holding a collection of interval specifications.
This means, that if we want to know how many of the values in L1
fall into the interval (80,90] we just need
to find the number of (80,90] entries in x.
We should notice, at this point, that x is really a collection of
Therefore we can return to
the things we learned in dealing with
discrete value, namely the use of the table()
function, to get the frequency of each of the discrete values.
y <- table( x ) has R
computing those frequencies and storing
the result in the valiable y.
Then, just as we did for the discrete case, we form a data frame
based on the values in our discrete variable y.
The display of our data frame, given in Figure 6,
has the same values as we had in the first two columns of
our goal table shown back in Table 2.
What remains to be done is to compute the remaining columns
and attach them to the data frame.
We start that process by adding the midpoint values that we have
already computed. The statement
df$midpnt <- x_mid appends those values as a new
column in the data frame, and it titles that column as
midpnt. The result is shown in Figure 7.
We continue the process by computing the
Recall that the relative frequency is the result
of dividing each of the particular frequencies by the number of
values in the original table. The command shown in Figure 8,
rf <- df$Freq/sum(df$Freq), finds the number of
values in the table by finding the sum of all the frequencies.
Comparing the values displayed in Figure 8 with those in the
fourth column of Table 2 confirms our calculation.
We just have to append those values to our data frame.
This is done in Figure 9.
We can use the View(df) command to
display, in the top left pane
within the RStudio window, pretty view of the data frame.
The command is shown in Figure 9.5.
And the pretty display is shown in Figure 10.
We continue the process by computing the cumulative frequency,
the relative cumulative frequency (which is mathematically identical to
the cumulative relative frequency), and the
number of degrees in a pie chart that should be allocated
to this group (assuming that we want to
make a pie chart which we know we should
not do). Figure 11 shows the specific
commands, and a final display of the data frame.
Also recall that as we change the data frame the
system automatically updates the "view" of the data frame.
Therefore, without having to perform another View(df)
command in our RStudio session, the "pretty"
view of the data frame now appears as in Figure 12.
To this point in the web page we have seen a sequence of steps in R
that we can use to go from an initial problem statement to
our desired solution. The process of those steps is to take
a collection of data, determine how we
can break the range of those values into equal width partitions,
count the frequency of the original data values in each of the
partitions, and form an expanded frequency table based on those
frequencies. The steps shown above from Figure 1 through Figure 12
walk us through that process. We could study, memorize, transcribe those
steps and then follow them whenever we have this kind of a problem.
Alternatively, as we did for discrete values, we could
write a function that just captures those steps so that we can
perform the steps by just calling the function.
The function that we created for the discrete case was called
make_freq_table() and it amounted to 22 lines of code, 9 of which were
comments. The function we create for this task,
the function collate3(), is a bit longer and more complex.
It does follow the same pattern that we have
just stepped through, but it also has a few small detours in it.
Rather than explain the function in a step by step manner here,
the listing of the function is provided both here and
on another web page, the latter providing a discussion of the
detailed structure and steps of the function.
The link to that other page is
Explanation of collate3.R.
The listing of the function here is:
collate3 <- function( lcl_list, use_low=NULL, use_width=NULL, ...)
## This is a function that will mimic, to some extent, a program
## that we had on the TI-83/84 to put a list of values into
## bins and then compute the frequeny, midpint, relative requency,
## cumulative frequency, cumuative relative frequency, and the
## number of degrees to allocate in a pie chart for each bin.
## One problem here is that getting interactive user input in R
## is a pain. Therefore, if the use_low, and or use_width
## parameters are not specified, the function returns summary
## information and asks to be run again with the proper values
lcl_real_low <- min( lcl_list )
lcl_real_high <- max( lcl_list )
lcl_size <- length(lcl_list)
if( is.null(use_low) | is.null(use_width) )
cat(c("The lowest value is ",lcl_real_low ,"\n"))
cat(c("The highest value is ", lcl_real_high,"\n" ))
suggested_width <- (lcl_real_high-lcl_real_low) / 10
cat(c("Suggested interval width is ", suggested_width,"\n" ))
cat(c("Repeat command giving collate3( list, use_low=value, use_width=value)","\n"))
return( "waiting..." )
## to get here we seem to have the right values
use_num_bins <- floor( (lcl_real_high - use_low)/use_width)+1
lcl_max <- use_low+use_width*use_num_bins
lcl_breaks <- seq(use_low, lcl_max, use_width)
lcl_mid<-seq(use_low+use_width/2, lcl_max-use_width/2, use_width)
lcl_cuts<-cut(lcl_list, breaks=lcl_breaks, ...)
lcl_freq <- table( lcl_cuts )
lcl_df <- data.frame( lcl_freq )
lcl_df$midpnt <- lcl_mid
lcl_df$relfreq <- lcl_df$Freq/lcl_size
lcl_df$cumulfreq <- cumsum( lcl_df$Freq )
lcl_df$cumulrelfreq <- lcl_df$cumulfreq / lcl_size
lcl_df$pie <- round( 360*lcl_df$relfreq, 1 )
If so desired, you could highlight that listing and copy it
and then paste it to your own
text editor, or even directly into an R or an RStudio
Furthermore, a link to the actual function file is
To illustrate using the collate3() function we start
with a source() command to load the function
into our RStudio session. Then we just try to run the
function to process the data that
we still have in L1 using the collate3(L1) command.
The result is shown in Figure 13.
It turns out that collate3()really wants us to
specify the starting point for our
partitions and the width of the partitions.
If we do not specify these, then
assumes that we just do not know them, probably because we do not know the
minimum and maximim values in the data.
[Now, in this case we did know them. We found them back in
However, we are trying to demonstrate using collate3()
without relying on our previous knowledge.]
In order to help us, collate3() does not just give up
but, rather, it provides us with the minimum and maximum values,
and it even suggests a width for the intervals.
We take that information and we reissue the command, this time
collate3( L1, 30, 10 ), telling the
function to process the values in L1 and to construct
partitions starting at 30 with a partition width of 10.
This is shown in Figure 14.
The result is the display of the data frame created by
collate3(). In one step we accomplished all of the
work that we went through in Figures 1 through 11 above.
It is worth doing this again but for different data values.
The whole process, other than loading the functions, is shown in
We use the command
gnrnd4( key1=1573429104, key2=19302340 )
to generate all new data, the command
L1 to display
that data, the command
collate3(L1) to find the
minimum and maximum values, the command
df <- collate3( L1, 190, 10) to create
a data frame giving the expanded frequency table
of the values in L1 based on
partitions starting at 190 and having a width of 10, soring that
data frame in the variable df, mand, finally,
getting the "pretty" display of that data frame shown
in Figure 16.
One feature that we have glossed over in this
discussion is the decision to use intervals that are
"closed on the right" as in a partition like (200,210].
In that partition, the value 210 would be part of the
partition, but the value 200 would not.
Instead, the value 200 is part of the partition (190,200].
What if we want to use partitions that are
"closed on the left"?
To do this the change actually has to go back to the cut()
statement, but rather than look there first, we note that
colalte3() was written with this in mind. In order to
have partitions that are closed on the left,
you simply change the collate3() command to
include the setting right=FALSE.
We have an example of this in Figure 17
where we repeat our command with the new setting.
The result is reflected in the "pretty" display
now shown in Figure 18.
As you can see in Figure 18 we are now using
partitions that are "closed on the left" an, as a result,
the frequencies change in some of the partitions.
What has really happened here is that the setting "right=FALSE"
was accepted by collate3() but passed on to the
cut() function inside of the collate3() function.
We can see how this setting changes the cut()
function by looking at the statements and results in Figure 19.
In Figure 19 we generate
and display a new L1, this time with just 14
Note that the tenth value is 175.0 and the fourteenth value is 135.0.
Then we generate a first version of the
partition this time using the default "closed on the right"
rule. Note that the tenth value here is
because that is where the tenth value in L1, 175.0, falls. The fourteenth
interval (bin, bucket, group) is
when closed on the right, that is where 135.0 falls.
After that we generate a similar partition, but this
time overriding that rule by setting right=FALSE.
The new partition is "closed on the left".
Note that tenth value here is
because, when closed on the left, that is where the tenth value in L1, 175.0, falls. The fourteenth
interval (bin, bucket, group) is
when closed on the left, that is where 135.0 falls.
Here is the list of the commands used to generate the R output on this page:
# the commands used on Frequency Tables -- Grouped Values
# First, we need to load the gnrnd4() function
# into our environment
# Then generate the values for table 1
gnrnd4( key1=1682089104, key2=0014000650 )
L1 # verify the data
# Get a summary of the data
summary( L1 )
# create some new variables just to hold
# some of the values we will use
low_val <- 30
high_val <- 110
step_val <- 10
# then create the "break" points
x_breaks <- seq( low_val, high_val, step_val )
# Now look at the break values
# While we are doing this we might as well set up
# the midpoint values for each of the intervals
# that we create
x_mid <- seq( low_val + step_val/2,
high_val - step_val/2, step_val )
x_mid # look at those values
# Now we are ready to find out into which interval each
# of the values in our original data, L1, gets put
x <- cut(L1, breaks=x_breaks)
# Now we are ready to start building our table. The
# variable x holds all those interval names, let us
# find out the frequency for each interval name
y <- table( x )
# let us see what is y
# We will create a data frame to hold all of our
# frequency table as we build it
df <- data.frame( y )
df # look at it as it has been created
# now append the mid points to the data frame
df$midpnt <- x_mid
df # see what the data frame looks like now
# Now construct the relative frequencies
rf <- df$Freq/length( L1 )
# append that to the data frame
df$relFreq <- rf
df # see what the data frame looks like now
# Let us look at the pretty version of the data frame
View( df ) # note the capital V
# The next columns to add are the cumulative frequency,
# the relative cumulative frequency, and the number of
# degrees to use in a pie chart for each interval
cs <- cumsum( df$Freq )
df$cumul <- cs # append cumulative sum
n <- length( L1 )
rcf <- cs/n
df$rel_cumul <- rcf # append rel cumul sum
df$pie <- round( 360*rf, 1 ) # append degrees in pie chart
# Then, rather than do all of that, we can load and
# run the collate3() function
# If we just give collate3 the list of values, L1,
# it just gives us help in finding the values that
# we need to give it in addition to the list
collate3( L1 )
# So we not take those suggestions but we will give
# collate3 the list, and the lowest value in the
# first interval, and the width of the interval
collate3( L1, 30, 10 )
# get new values and build frequency table for them
gnrnd4( key1=1573429104, key2=19302340 )
df <- collate3( L1, 190, 10 )
View( df )
# The default setting is to have the intervals closed
# on the right. Get intervals closed on the left.
df <- collate3( L1, 190, 10, right=FALSE)
# Just to examine the difference in the cuts
# Generate some new values
gnrnd4( 1778231304, 15901453)
L1 # to look at the values
# create the cut points
cut_pnts <- seq( 100,180, 5)
# use the default cuts (close on right)
cut( L1, cut_pnts )
# do it again but this time closed on the left
cut( L1, cut_pnts, right=FALSE)
Return to Topics
©Roger M. Palay
Saline, MI 48176 September, 2019