Function for simple navigation of dataframe in console I was looking for a way to navigate through a dataframe in R using just the console that displays every n lines. There are a few functions you can find at StackOverflow that attempt to answer this but none are quite what I want. So I made my own function but it has a lot of if statements. How can I refactor my code to make it more concise and clean?

Note: I chose "." and "/" for page up and page down because code completion in RStudio automatically turns "[" into "[ ]". I'm open to any alternative symbols.

Here is the code. It is called "less" but it is obviously missing most of the functions in the Unix version of less.

less &lt;- function(x, n = 10) {
  begin = 1
  end = n

  repeat {
    print(
      x[begin:end, ]
    )

    input &lt;- readline("Type . to scroll up, type / to scroll down: ")

    if(input == ".") {
      begin = begin - n
      end = end - n
    }

    if(input == "/") {
      begin = begin + n
      end = end + n
    }

    if(end &gt;= nrow(x)) break

    if(end &lt; 1) break
  }
}
Without changing the interface too much, your code could use the following changes. Note that I am fixing three bugs regarding corner cases:


if your data has fewer than n rows to start with (see where I used min)
if your data has a single column (see where I used drop = FALSE)
make sure that the last page will always show


I avoided duplicating the statements  where begin and end are reset, by creating an increment variable. The code computing the increment (of +n or -n) has been moved to its own function. One improvement I've made is that the function will call itself again via Recall() in case the user entered something other than the expected . or /, i.e. it will prompt the user again.

less &lt;- function(x, n = 10) {
  begin &lt;- 1
  end   &lt;- n

  get_increment &lt;- function() {
    input &lt;- readline("Type . to scroll up, type / to scroll down: ")
    if (input == ".") -n else if (input == "/") +n else Recall()
  }

  repeat {
    print(x[begin:min(end, nrow(x)), , drop = FALSE])

    increment &lt;- get_increment()
    begin     &lt;- begin + increment
    end       &lt;- end   + increment

    if(begin &gt; nrow(x) | end &lt; 1) break
  }
}


Now regarding your design in general, I feel it would be nice to pre-split your data.frame every n rows, and let the user know that there are for example 12 pages and that he is currently looking at page 4/12. In the prompt, also allow the user to jump to a specific page in addition to the next or previous page. Finally, make it easier to exit via a specific code like "q" rather than wait for the user to go beyond the first or last page. Here is my attempt:

less &lt;- function(x, n = 10) {

   N &lt;- nrow(x)
   if (N == 0) {
      message("input has no rows")
      return(NULL)
   }

   row_page_idx &lt;- (seq_len(N) - 1) %/% n
   pages &lt;- split(x, row_page_idx)
   num_pages &lt;- length(pages)
   page_idx &lt;- 1

   get_new_page_idx &lt;- function() {
      input &lt;- readline('Type a page number, "&gt;" for next, "&lt;" for previous, or "q" to quit: ')
      if (input == "q") return(0)
      if (input == "&gt;") return(min(num_pages, page_idx + 1))
      if (input == "&lt;") return(max(1, page_idx - 1))
      integer &lt;- suppressWarnings(as.integer(input))
      if (!is.na(integer) &amp; integer %in% 1:num_pages) return(integer)
      Recall() # if all fails
   }

   repeat {
      print(pages[[page_idx]])
      message("Page ", page_idx, " of ", num_pages)
      page_idx &lt;- get_new_page_idx()
      if(page_idx == 0) break
   }
}