oldtimetrad
oldtimetrad

Reputation: 145

How to write a function that will return the output as an S3 Class

I am new to R and I am having some difficulty understanding how to return the output of a S3 class for a function. I have some text and I need to write a summary method for it that will count the number of words in the text and the frequency of the top 3 words in the text. I have a function countwords that will count the words. The text is above the code:

 text = 'The time of year was spring the sun shone for the birds who were not singing yet. The Local farmer was out in the fields preparing for the summer ahead. He had a spring in his step, for he was whistling.'

    #counts the number of words in the text
    countwords = function(x) {
      # Read in the words from the text and separate into a vector
      txt = unlist(strsplit(x,' '))
      # Loop through each word
      k = 0
      for(i in 1:length(txt)) {
        k = k + 1
      }

      return(k)
    }
    countwords(firstpar)

How do I return the output of this as an s3 class? How do I write a summary method/function? to count the words and also the top 3 words in the text? I am new to R and need some help explaining S3 classes and methods and functions. Is a function the same as a method?

Thank you

Upvotes: 2

Views: 888

Answers (2)

Gavin Simpson
Gavin Simpson

Reputation: 174778

Here's one way to do both things, which illustrates the way to add a class and not have to write all the methods you might need for that class. I've also tweaked your function a bit to be more efficient and to work on a vector of strings as inputs. You also don't need the return() call; IIRC it is slightly more efficient to not call return explicitly but to use the fact that R returns automatically the result of the final statement in the function.

mystring <- "The time of year was spring the sun shone for the birds who were not singing yet. The Local farmer was out in the fields preparing for the summer ahead. He had a spring in his step, for he was whistling."

# counts the number of words in the text
countwords <- function(x) {
  # Read in the words from the text and separate into a vector
  txt <- strsplit(x, " ")
  n <- sapply(txt, length)
  top3 <- lapply(txt, function(x) names(tail(sort(table(x)), 3)))
  out <- list(n = n, top3 = top3)
  class(out) <- c("mysummary", "list")
  out # implied that we return out here
}

countwords(mystring)

This gets us:

> countwords(mystring)
$n
[1] 41

$top3
$top3[[1]]
[1] "for" "was" "the"


attr(,"class")
[1] "mysummary" "list"

Which isn't pretty, but we can sort that later with a print method. Notice that this is just a list, hence I used class(out) <- c("mysummary", "list") as my S3 class(es) to indicate inheritance from class "list"

> str(countwords(mystring))
List of 2
 $ n   : int 41
 $ top3:List of 1
  ..$ : chr [1:3] "for" "was" "the"
 - attr(*, "class")= chr [1:2] "mysummary" "list"

That means we can subset it like any list without writing those methods:

> cw <- countwords(mystring)
> cw$n
[1] 41

> cw[[2]]
[[1]]
[1] "for" "was" "the"

That's all you really need for an S3 class. This doesn't change even if you stick this in a package. (What you need to do extra then relates to methods for ytou class and we don't have any of those as we inherit from class "list"

> inherits(cw, "list")
[1] TRUE

If you want to add a print method we can just do:

`print.mysummary` <- function(x, ...) {
  writeLines(strwrap("Number of words:", prefix = "\n"))
  print(x$n, ...)
  writeLines(strwrap("Top 3 Words:", prefix = "\n"))
  print(x$top3, ...)
  invisible(x) 
}

which then produces:

> cw

Number of words:
[1] 41

Top 3 Words:
[[1]]
[1] "for" "was" "the"

Upvotes: 4

keegan
keegan

Reputation: 2992

To make @Roland's comments a little more explicit:

First, you'll create an S3 class of your own, let's call it myTextClass, and then assign the class attribute of your object text.

class(text)<-c('myTextClass')

At this point, the class myTextClass isn't doing much for us, so we need to make a method for this class. In particular, we'll make a new method for the summary function so that whenever summary encounters and object of class myTextClass it will execute our desired method instead of any others.

summary.myTextClass<-function(t){
    z1<- paste('Length of text is ', length(unlist(strsplit(t,' '))) ,sep=' ')

    tb<-table(unlist(strsplit(t,' ')))
    topWords<-paste(names(tb)[1:3],tb[1:3],sep=':')
    z2<- paste(c('Top words are... ', topWords) ,collapse=' ')

    return(c(z1,z2))
}

This method does some of the basic things you mention such as word counts and so on. Now when we call the generic function summary on an object of class myTextClass, this particular method will be called.

summary(text)
[1] "Length of text is  41"                  "Top words are...  a:1 ahead.:1 birds:1" 

Upvotes: 0

Related Questions