9.7 Investigate Your Object: str() and Lists

Let’s reconsider the Meetup Simulation from Section 6.4:

meetupSim <- function(reps = 10000, table = FALSE, seed = NULL) {
  if ( !is.null(seed) ) {
    set.seed(seed)
  }
  anna <- runif(reps, 0, 60)
  raj <- runif(reps, 0, 60)
  connect <- (abs(anna - raj) < 10)
  if ( table ) {
    cat("Here is a table of the results:\n\n")
    print(table(connect))
    cat("\n")
  }
  cat("The proportion of tims they met was ", mean(connect), ".\n", sep = "")
}

You will recall that when the user asks for a table of results, the function prints out a table that looks like this:

## Here is a table of the results:

## connect
## FALSE  TRUE 
## 69781 30219 

There are a couple of small irritations, here:

  • The name of the table (“connect”) appears in the output, even though it was a name that was given in the code internal to the function. As a name for the output-table, it’s not the most descriptive choice. Besides, we really don’t need a name here, because have just cat-ed out a sentence that introduces the table.
  • The names for the columns (FALSE and TRUE) again pertain to features internal to the code of the function. The user should see more descriptive names.

In order to investigate how we might deal with these issues, let’s create a small table here:

logicalVector <- c(rep(TRUE, 6), rep(FALSE, 4))
tab <- table(logicalVector)
tab
## logicalVector
## FALSE  TRUE 
##     4     6

One way to deal with the column-name issues might be to isolate each table value and then repackage the values. We can access the individual table-values with sub-setting. For example, the first value is:

tab[1]
## FALSE 
##     4

Hence we could grab the values, create a vector from them, and then provide names for the vector that we like. Thus:

results <- c(tab[1], tab[2])
names(results) <- c("did not meet", "met")
results
## did not meet          met 
##            4            6

Another approach—and this is the more instructive and generally-useful procedure—is to begin by looking carefully at the structure of the problematic object:

str(tab)
##  'table' int [1:2(1d)] 4 6
##  - attr(*, "dimnames")=List of 1
##   ..$ logicalVector: chr [1:2] "FALSE" "TRUE"

We see that

  • the table has an attribute called dimnames
  • dimnames is a list of length one.
  • It is a named list. The name of its only element is logicalVector.
  • The elements of this vector are the column names for the table.

If you would like to see the dimnames attribute all by itself, you can access it with the attr() function :

attr(tab, which = "dimnames")  # "which" says which attribute you want!
## $logicalVector
## [1] "FALSE" "TRUE"

You can also use attr() to set the values of an attribute. Here, we want dimnames to be a list of length one that does not have a name for its sole element. The following should do the trick:

attr(tab, which = "dimnames") <- list(c("did not meet", "met"))

Let’s see if this worked:

tab
## did not meet          met 
##            4            6

It appears to have worked very nicely! Hence we may rewrite meetupSim() as follows:

meetupSim <- function(reps = 10000, table = FALSE, seed = NULL) {
  if ( !is.null(seed) ) {
    set.seed(seed)
  }
  anna <- runif(reps, 0, 60)
  raj <- runif(reps, 0, 60)
  connect <- (abs(anna - raj) < 10)
  if ( table ) {
    cat("Here is a table of the results:\n\n")
    tab <- table(connect)
    attr(tab, which = "dimnames") <- list(c("did not meet", "met"))
    print(tab)
    cat("\n")
  }
  cat("The proportion of tims they met was ", mean(connect), ".\n", sep = "")
}

Let’s try it out:

meetupSim(reps = 100000, table = TRUE, seed = 3939)
## Here is a table of the results:
## 
## did not meet          met 
##        69781        30219 
## 
## The proportion of tims they met was 0.30219.

Much better!

The moral of the story is:

Make a habit of examining your objects with the str() function. Combining str() with your abilities to manipulate lists allows you to access and set pieces of the object in helpful ways.

Note: the dimnames attribute for tables and matrices is so frequently used that it has its own special function for accessing and setting: dimnames(). Other popular attributes, such as names for a vector and levels for a factor, also have dedicated access/set functions—names() and levels() respectively. But keep in mind that you can access and set the values for any attribute at all with the attr() function.

9.7.1 Practice Exercises

  1. Consider the following matrix:

    myMat <- matrix(1:24, nrow = 4)
    rownames(myMat) <- letters[1:4]
    colnames(myMat) <- LETTERS[1:6]
    myMat
    ##   A B  C  D  E  F
    ## a 1 5  9 13 17 21
    ## b 2 6 10 14 18 22
    ## c 3 7 11 15 19 23
    ## d 4 8 12 16 20 24

    Find a way to change the row names of myMAT to “x,” “y,” “z” and “w,” using the attr() function rather than the rownames() function.

9.7.2 Solutions to Practice Exercises

  1. First, run str(myMat). You find that it has an attribute called dimnames that is a list of length 2. The first element of this list is the vector of row names. Hence you need to assign new row names to this element. You can do so as follows:

    attr(myMat, which = "dimnames")[[1]] <- c("x", "y", "z", "w")
    myMat
    ##   A B  C  D  E  F
    ## x 1 5  9 13 17 21
    ## y 2 6 10 14 18 22
    ## z 3 7 11 15 19 23
    ## w 4 8 12 16 20 24

    It worked!