10

I'm facing an unexpected behavior with base R.

I want to retrieve the first row of a dataframe with its colnames as a vector, and this common method works perfectly for most cases.

df = data.frame(A=c(12,13), B=c(24,25))
unlist(df[1,]) #class=numeric
#    A  B
#   12 24

But when the dataframe is only composed of one column, the result is coerced to an unnamed vector:

df = data.frame(A=c(12,13))
unlist(df[1,]) #class=numeric too
# 12

How to keep the name is the second case?

Dan Chaltiel
  • 7,811
  • 5
  • 47
  • 92

4 Answers4

5

When extracting a dataframe using [ by default drop argument is TRUE.

From ?Extract

drop - If TRUE the result is coerced to the lowest possible dimension.

Also you can check the class for both the dataframe after extracting the row.

df1 = data.frame(A=c(12,13), B=c(24,25))
df2 = data.frame(A=c(12,13))

class(df1[1, ])
#[1] "data.frame"
class(df2[1, ])
#[1] "numeric"

As we can see df2 is coerced to a vector. Using drop = FALSE will keep it as dataframe and not drop the dimensions.

df2[1,, drop = FALSE]
#   A
#1 12

class(df[1,, drop = FALSE])
#[1] "data.frame"
Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
4

If you want a named vector, then a single column data frame can be tricky to extract. A potential workaround can be,

do.call(c, df)[1]
#A1 
#12 

Where,

str(do.call(c, df)[1])
 Named num 12
 - attr(*, "names")= chr "A1"

Note that since it converts all the values to named vector, it adds a numeric suffix after the name so it does not have duplicated names

Sotos
  • 51,121
  • 6
  • 32
  • 66
2

It's R default behavior.

By default R will convert single column data.frames into vectors (with drop = TRUE). It's a good habit to use the option drop = FALSE when dealing with data.frames. This way you can be safe that data type will not change.

So, instead of:

df[1,]

Use:

df[1, , drop = FALSE]

Hope this helps.

Louis
  • 3,592
  • 2
  • 10
  • 18
2

From dplyr

?slice

Choose rows by their ordinal position


library(dplyr)
slice(df, 1L)

#   A
#1 12

str(slice(df, 1L))
# 'data.frame': 1 obs. of  1 variable:
#$ A: num 12

If you want it to be a named number, you can unlist it.

str(unlist(slice(df, 1L)))
#Named num 12
# - attr(*, "names")= chr "A"

You can as well transpose it keeping column names.

colnames(t(unlist(slice(df, 1L))))

#"A"
deepseefan
  • 3,701
  • 3
  • 18
  • 31
  • 1
    `dplyr` FTW! My question was base R so I cannot accept this answer, but this feels much cleaner – Dan Chaltiel Nov 21 '19 at 11:02
  • This is just an alternative and for base r @Ronak's solution can be tailored `t(unlist(df[1,, drop = FALSE]))` to produce the same result when transposed. – deepseefan Nov 21 '19 at 11:06