Unwanted leading whitespace before number to char conversion in R

Viewed 32

Consider the following minimal working example:

df <- tibble::tibble(
    x=c(1, 10)
)

mwe <- function(row) {
    return(paste0(row[["x"]]))
}

df$mwe1 = apply(df, 1, mwe)
df$mwe2 = apply(df, 1, mwe)

The final value of df is

# A tibble: 2 × 3
      x mwe1  mwe2 
  <dbl> <chr> <chr>
1     1 1     " 1" 
2    10 10    "10" 

I expect it to be

# A tibble: 2 × 3
      x mwe1  mwe2 
  <dbl> <chr> <chr>
1     1 1     1 
2    10 10    10 

Can I get an explanation of why column mwe2 has the extra whitespace?

R.version info

platform       x86_64-pc-linux-gnu         
arch           x86_64                      
os             linux-gnu                   
system         x86_64, linux-gnu           
status                                     
major          4                           
minor          1.2                         
year           2021                        
month          11                          
day            01                          
svn rev        81115                       
language       R                           
version.string R version 4.1.2 (2021-11-01)
nickname       Bird Hippie
2 Answers

Some details behind apply():

  1. apply() converts a data.frame(or tibble) into a matrix with as.matrix() before applying a function over array margins.
  2. If a dataframe contains numeric and character columns, as.matrix() will make all numeric ones converted to character with format().
  3. format() coerces a numeric vector to have the same width when convering it into character. E.g.
format(c(123.45, pi))

[1] "123.450000"  "  3.141593"

(Note the trailing zeros of 123.45 and the leading whitespaces of pi)


When you create mwe1:

df$mwe1 = apply(df, 1, mwe)

df just has a numeric column x, so only step 1 is involved. The process goes on to mwe2:

df$mwe2 = apply(df, 1, mwe)

Now df has two columns, one is numeric(x) and the other character(mwe1). In this case, step 2-3 are involved and hence the x column will be passed to format() and become

format(df$x)

[1] " 1" "10"

This is why the leading whitespace appears.

The meaning of 1 changes between when you create mwe1 and mwe2 because the new column changes the row. That is, having the mwe1 column changes the way paste works.

When you are creating mwe1 the data frame consists of one column (x) and 2 rows. When you are creating mwe2 the data frame has two columns and two rows. So when you paste0() the rows the result is different.

Update

First it is an illusion that the creation of mwe1 is working as you expect. It is creating a character column but because there is no whitespace the print method is not putting the quotation marks. If you print the tibble you will see that the column type for mwe1 is character.

Let's look more closely.

mwe <- function(row) {
  paste0(row[["x"]])
}
mwe_r1 <- function(row) {
  paste0(trimws(row[["x"]]))
}
mwe_r2 <- function(row) {
    as.numeric(paste0(row[["x"]]))
}

df <- tibble::tibble(
  x=c(1, 10)
)
# original tibble
df

df0 <- df
df0$mwe1 = apply(df0, 1, mwe)
df0$mwe2 = apply(df0, 1, mwe)
#tibble with mwe2 added
df0

dfr1 <- df
dfr1$mwe1 = apply(dfr1, 1, mwe_r1)
dfr1$mwe2 = apply(dfr1, 1, mwe_r1)

dfr1

dfr2 <- df
dfr2$mwe1 = apply(dfr1, 1, mwe_r2)
dfr2$mwe2 = apply(dfr1, 1, mwe_r2)

dfr2

Here are the results

> df0
# A tibble: 2 × 3
      x mwe1  mwe2 
  <dbl> <chr> <chr>
1     1 1     " 1" 
2    10 10    "10" 

> dfr1
# A tibble: 2 × 3
      x mwe1  mwe2 
  <dbl> <chr> <chr>
1     1 1     1    
2    10 10    10   

> dfr2
# A tibble: 2 × 3
      x  mwe1  mwe2
  <dbl> <dbl> <dbl>
1     1     1     1
2    10    10    10

Notice as mentioned that df0 is what you had and the two new columns are both chr but they print differently.

Then dfr1 simply removes the whitespace before pasting. This gives the illusion that both new columns are numeric but they are actually both character.

Finally dfr2 is what you want, which is two new numeric columns. This is because the mwe_r2 function explicitly coerces the results of the paste to numeric.

If you read ?paste you will see that the function explicitly is described:

Concatenate Strings Description Concatenate vectors after converting to character.

paste0() is always going to give you the results as character. That is exactly what is happening. Therefore you need to coerce the results to numeric if you want numeric data.

Related