vagabond vagabond - 1 month ago 10
R Question

Why does subsetting a data frame vs. a tibble give different results

This is a 'why' question and not a 'How to' question.

I have a

tibble
as a result of an aggregation
dplyr


> str(urls)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 144 obs. of 4 variables:
$ BRAND : chr "Bobbi Brown" "Calvin Klein" "Chanel" "Clarins" ...
$ WEBSITE : chr "http://www.bobbibrowncosmetics.com/" "http://www.calvinklein.com/shop/en/ck" "http://www.chanel.com/en_US/" "http://www.clarinsusa.com/" ...
$ domain : chr "bobbibrowncosmetics.com/" "calvinklein.com/shop/en/ck" "chanel.com/en_US/" "clarinsusa.com/" ...
$ final_domain: chr "bobbibrowncosmetics.com/" "calvinklein.com/shop/en/ck" "chanel.com/en_US/" "clarinsusa.com/" ...


When I try to extract the column final_domain as a character vector here's what happens:

> length(as.character(urls[ ,4]))
[1] 1


When I instead, coerce to data frame and then do it, I get what I actually want:

> length(as.character(as.data.frame(urls)[ ,4]))
[1] 144


The
str
of the tibble vs. dataframe looks the same but output differs. I'm wondering why?

Answer

The underlying reason is that subsetting a tbl and a data frame produces different results when only one column is selected.

  • By default, [.data.frame will drop the dimensions if the result has only 1 column, similar to how matrix subsetting works. So the result is a vector.
  • [.tbl_df will never drop dimensions like this; it always returns a tbl.

In turn, as.character ignores the class of a tbl, treating it as a plain list. And as.character called on a list acts like deparse: the character representation it returns is R code that can be parsed and executed to reproduce the list.

The tbl behaviour is arguably the right thing to do in most circumstances, because dropping dimensions can easily lead to bugs: subsetting a data frame usually results in another data frame, but sometimes it doesn't. In this specific case it doesn't do what you want.

If you want to extract a column from a tbl as a vector, you can use list-style indexing: urls[[4]] or urls$final_domain.

Comments