I would like to take a data frame with characters and numbers, and concatenate all of the elements of the each row into a single string, which would be stored as a single el
For those using library(tidyverse), you can simply use the unite function.
new.df<-df%>%
unite(together, letters, numbers, sep="")
This will give you a new column called "together" with A1, B2, etc
if you want to start with
df <- data.frame(letters = LETTERS[1:5], numbers = 1:5, stringsAsFactors=TRUE)
.. then there is no general rule about how df$letters
will be interpreted by any given function. It's a factor for modelling functions, character for some and integer for some others. Even the same function such as paste may interpret it differently, depending on how you use it:
paste(df[1,], collapse="") # "11"
apply(df, 1, paste, collapse="") # "A1" "B2" "C3" "D4" "E5"
No logic in it except that it will probably make sense once you know the internals of every function.
The factors seem to be converted to integers when an argument is converted to vector (as you know, data frames are lists of vectors of equal length, so the first row of a data frame is also a list, and when it is forced to be a vector, something like this happens:)
df[1,]
# letters numbers
# 1 A 1
unlist(df[1,])
# letters numbers
# 1 1
I don't know how apply
achieves what it does (i.e., factors are represented by character values) -- if you're interested, look at its source code. It may be useful to know, though, that you can trust (in this specific sense) apply
(in this specific occasion). More generally, it is useful to store every piece of data in a sensible format, that includes storing strings as strings, i.e., using stringsAsFactors=FALSE
.
Btw, every introductory R book should have this idea in a subtitle. For example, my plan for retirement is to write "A (not so) gentle introduction to the zen of data fishery with R, the stringsAsFactors=FALSE way".
This is indeed a little weird, but this is also what is supposed to happen.
When you create the data.frame
as you did, column letters
is stored as factor
. Naturally factors have no ordering, therefore when as.numeric()
is applied to a factor it returns the ordering of of the factor. For example:
> df[, 1]
[1] A B C D E
Levels: A B C D E
> as.numeric(df[, 1])
[1] 1 2 3 4 5
A
is the first level of the factor df[, 1]
therefore A
gets converted to the value 1
, when as.numeric
is applied. This is what happens when you call paste(df[1, ])
. Since columns 1 and 2 are of different class, paste first transforms both elements of row 1 to numeric then to characters.
When you want to concatenate both columns, you first need to transform the first row to character:
df[, 1] <- as.character(df[, 1])
paste(df[1,], collapse = "")
As @sebastian-c pointed out, you can also use stringsAsFactors = FALSE
in the creation of the data.frame, then you can omit the as.character()
step.
While others have focused on why your code isn't working and how to improve it, I'm going to try and focus more on getting the result you want. From your description, it seems you can readily achieve what you want using paste:
df <- data.frame(letters = LETTERS[1:5], numbers = 1:5, stringsAsFactors=FALSE)
paste(df$letters, df$numbers, sep=""))
## [1] "A1" "B2" "C3" "D4" "E5"
You can change df$letters
to character using df$letters <- as.character(df$letters)
if you don't want to use the stringsAsFactors
argument.
But let's assume that's not what you want. Let's assume you have hundreds of columns and you want to paste them all together. We can do that with your minimal example too:
df_args <- c(df, sep="")
do.call(paste, df_args)
## [1] "A1" "B2" "C3" "D4" "E5"
I realised the problem you're having is a combination of the fact that you're using a factor and that you're using the sep
argument instead of collapse
(as @adibender picked up). The difference is that sep
gives the separator between two separate vectors and collapse
gives separators within a vector. When you use df[1,]
, you supply a single vector to paste
and hence you must use the collapse
argument. Using your idea of getting every row and concatenating them, the following line of code will do exactly what you want:
apply(df, 1, paste, collapse="")
Ok, now for the explanations:
Why won't as.list
work?
as.list
converts an object to a list. So it does work. It will convert your dataframe to a list and subsequently ignore the sep=""
argument. c
combines objects together. Technically, a dataframe is just a list where every column is an element and all elements have to have the same length. So when I combine it with sep=""
, it just becomes a regular list with the columns of the dataframe as elements.
Why use do.call
?
do.call
allows you to call a function using a named list as its arguments. You can't just throw the list straight into paste
, because it doesn't like dataframes. It's designed for concatenating vectors. So remember that dfargs
is a list containing a vector of letters, a vector of numbers and sep which is a length 1 vector containing only "". When I use do.call
, the resulting paste function is essentially paste(letters, numbers, sep)
.
But what if my original dataframe had columns "letters", "numbers", "squigs", "blargs"
after which I added the separator like I did before? Then the paste function through do.call
would look like:
paste(letters, numbers, squigs, blargs, sep)
So you see it works for any number of columns.