Whether at work or for personal projects, I use ESS a lot to perform interactive data analyses. The ability to write, edit, and submit R commands to an interactive R process is simply something I cannot imagine analyzing data without.
One thing that I end up having to do a lot is inspect an object that I have just assigned to a variable in R. To fix ideas, let us create a data.frame called df for this example.
> df <- data.frame(patient = 1:100, age = rnorm(100, mean = 10, sd = 6), sex = sample(gl(2, 50, labels = c("Male", "Female"))))
I just created the data.frame df, and I want to know if I did it correctly. For instance, does it look like I expect it to? Does it have 100 observations like I want? Do the variables have the right names? Is the sex variable a factor with two levels? In short, I want to call the str function using the object df as an argument.
Here is the output I am interested in seeing:
> str(df) 'data.frame': 100 obs. of 3 variables: $ patient: int 1 2 3 4 5 6 7 8 9 10 ... $ age : num 11.06 7.73 17.61 3.11 6.76 ... $ sex : Factor w/ 2 levels "Male","Female": 2 1 1 2 2 1 1 2 2 2 ...
Inspecting objects the old way
So, how can I quickly see the structure as shown above? One idea is to switch over to my interactive R buffer in Emacs, type the command at the prompt, and then switch back to my code buffer to edit the data.frame command or continue programming. I dislike having to switch back and forth between the buffers for a one-off command though.
Alternatively, I could type str(df) in my code buffer, evaluate it, and decide to keep it or delete the line. Since this is more of a quick check, without permanent results, I usually will not want to keep lines like this around, since they clutter up my program. Typically, I am writing the program to be later run in BATCH mode, so I also do not want functions like that in my code since some can be excessively time-consuming depending on the size of the data.frame.
Another option is to use the ESS function ess-execute-in-tb, by default this is bound to C-c C-t, which will prompt me for an expression to evaluate. This is nice because I do not have to clutter my buffer with extraneous function calls. However, after using this method for a while, I noticed that I had many patterns with my objects. For data.frames, I would almost always use summary or str on them after assignment. For factors, I would want to table the values after I created them, to be sure they looked right. For numeric vectors, I would want to summarize them. I also wanted to summarize model fits (e.g., lm). I wanted to take advantage of my usage patterns so that I did not have to type so much after assigning an object to a variable.
Inspecting objects the new way
I therefore wrote an Emacs Lisp function that, when called via a key chord in Emacs, inspects the object at point, determines the class of that object, and based on the class, calls an R function on that object, showing the results in a tooltip. For the df example above, I would just put point on “df”, anywhere in the source code, and type C-c C-g (my default binding). A tooltip is then shown with the output of str(df).
An example similar to this, along with several others are shown in this screencast. I think this is the best way to show how my Lisp function interacts with R to show object information in tooltips.
Pretty nice! One thing to note is that the tooltips are displaying in a proportional font, not a monospace one. I know at some point I had found a customizable variable to specify which font tooltips display in, but I apparently did not save it. If I find that variable, I will update this post to reflect how to do that.
The Emacs Lisp function and keybinding
Here is the code you will need for this behavior. It depends on having tooltip-show-at-point defined, which is found only in ESS 5.4 (the current version as of this post) or later. I contributed tooltip-show-at-point to the ESS project a few months ago. It is used to show argument tooltips when you type an opening parenthesis. Perhaps my object tooltip function will find its way into a future version of ESS. Here is the code.
;; ess-R-object-tooltip.el ;; ;; I have defined a function, ess-R-object-tooltip, that when ;; invoked, will return a tooltip with some information about ;; the object at point. The information returned is ;; determined by which R function is called. This is controlled ;; by an alist, called ess-R-object-tooltip-alist. The default is ;; given below. The keys are the classes of R object that will ;; use the associated function. For example, when the function ;; is called while point is on a factor object, a table of that ;; factor will be shown in the tooltip. The objects must of course ;; exist in the associated inferior R process for this to work. ;; The special key "other" in the alist defines which function ;; to call when the class is not mached in the alist. By default, ;; the str function is called, which is actually a fairly useful ;; default for data.frame and function objects. ;; ;; The last line of this file shows my default keybinding. ;; I simply save this file in a directory in my load-path ;; and then place (require 'ess-R-object-tooltip) in my .emacs ;; the alist (setq ess-R-object-tooltip-alist '((numeric . "summary") (factor . "table") (integer . "summary") (lm . "summary") (other . "str"))) (defun ess-R-object-tooltip () "Get info for object at point, and display it in a tooltip." (interactive) (let ((objname (current-word)) (curbuf (current-buffer)) (tmpbuf (get-buffer-create "**ess-R-object-tooltip**"))) (if objname (progn (ess-command (concat "class(" objname ")n") tmpbuf ) (set-buffer tmpbuf) (let ((bs (buffer-string))) (if (not(string-match "(object .* not found)|unexpected" bs)) (let* ((objcls (buffer-substring (+ 2 (string-match "".*"" bs)) (- (point-max) 2))) (myfun (cdr(assoc-string objcls ess-R-object-tooltip-alist)))) (progn (if (eq myfun nil) (setq myfun (cdr(assoc-string "other" ess-R-object-tooltip-alist)))) (ess-command (concat myfun "(" objname ")n") tmpbuf) (let ((bs (buffer-string))) (progn (set-buffer curbuf) (tooltip-show-at-point bs 0 30))))))))) (kill-buffer tmpbuf))) ;; my default key map (define-key ess-mode-map "C-cC-g" 'ess-R-object-tooltip) (provide 'ess-R-object-tooltip)
Notice that you can add your own object classes and functions fairly easily at the top of the program. There is a special “other” class which will be called for classes not defined otherwise.
Further meta-data features in ESS?
If you can think if anymore examples for types of objects that this would be useful for, feel free to post them in the comments. I think this is a very useful feature when interactively examining datasets, fitting models, and analyzing data. In general, I think there are many more interesting ways to have meta-data on objects available quickly within the ESS and R system. I will be sure to share them as I explore ways to more efficiently do statistical analysis within the R environment.