数据框中的行连接

48

我想将包含字符和数字的数据框的每一行元素都连接成一个字符串,并将其作为单个元素存储在向量中。例如,我创建了一个字母和数字的数据框,然后希望通过paste函数拼接第一行,并返回值"A1"。

df <- data.frame(letters = LETTERS[1:5], numbers = 1:5)
df

##   letters numbers
## 1       A       1
## 2       B       2
## 3       C       3
## 4       D       4
## 5       E       5

paste(df[1,], sep =".")
## [1] "1" "1"

因此,粘贴将行的每个元素转换为整数,该整数对应于“相应级别的索引”,就像它是一个因子一样,并将其保持为长度为2的向量。(我知道/相信强制将因子转换为字符的因子会以这种方式进行,但由于R根本未将df [1,]存储为因子(通过is.factor()测试),我无法验证它实际上是一个级别的索引)

is.factor(df[1,])
## [1] FALSE
is.vector(df[1,])
## [1] FALSE

如果它不是一个向量,那么它的表现异常就有意义了,但我无法将其强制转换为向量。

> is.vector(as.vector(df[1,]))
[1] FALSE

使用as.character似乎无法帮助我解决问题。

有人可以解释一下这种行为吗?


你尝试过在创建数据框时添加 stringsAsFactors=FALSE 参数吗? - sebastian-c
4个回答

71

虽然其他人可能聚焦于为什么你的代码不能工作以及如何改进它,但我将尝试更加聚焦于帮助你获得你想要的结果。从你的描述来看,使用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"

如果您不想使用stringsAsFactors参数,您可以使用df$letters <- as.character(df$letters)df$letters更改为字符。

但假设这不是您想要的。假设您有数百列,并且想将它们全部粘贴在一起。我们也可以用您的最小示例来实现:

df_args <- c(df, sep="")
do.call(paste, df_args)

## [1] "A1" "B2" "C3" "D4" "E5"

编辑:另一种方法和说明:

我意识到你遇到的问题是因为你在使用一个因子(factor)以及使用sep参数而不是collapse(正如@adibender所指出的)。区别在于,sep给出两个独立向量之间的分隔符,而collapse给出向量内的分隔符。当你使用df[1,]时,你提供了一个单一向量给paste,因此你必须使用collapse参数。使用你获取每行并连接它们的想法,以下代码将完全达到你的目的:

apply(df, 1, paste, collapse="")

好的,现在开始解释:

为什么不能使用as.list

as.list将一个对象转换为列表。所以它是有效的。它将把你的数据框转换为一个列表,并随后忽略sep = ""参数。c将对象组合在一起。从技术上讲,数据框只是一个列表,其中每一列都是一个元素,所有元素的长度必须相同。因此,当我将其与sep = ""组合使用时,它只变成了一个普通列表,其中数据框的列作为元素。

为什么要使用do.call

do.call允许您使用命名列表作为其参数调用函数。您不能直接将列表扔到paste中,因为它不喜欢数据帧。它被设计用于连接向量。因此,请记住,dfargs是一个包含字母向量、数字向量和长度为1的只包含""的向量的列表。当我使用do.call时,得到的粘贴函数实际上是paste(letters, numbers, sep)
但是,如果我的原始数据框具有列"letters","numbers","squigs","blargs",然后像之前一样添加分隔符,那么通过do.call的粘贴函数将如下所示:

paste(letters, numbers, squigs, blargs, sep)

所以你可以看到它适用于任何列数。


谢谢,那个像谚语一样完美地运作了。你能详细解释一下为什么通过“c”运算符转换为列表与使用as.list()不同,并且为什么你使用do.call()调用paste而不是直接使用paste()吗?显然这些选项不起作用,但从直觉上看它们应该是可以的。 - Sam
谢谢Sebastian-c!我因为使用sep = ""而不是collapse = ""而陷入疯狂的'apply'中。 - Davit Sargsyan

6

对于使用 library(tidyverse) 的用户,您可以直接使用 unite 函数。

 new.df <- df%>%
 unite(together, letters, numbers, sep="")

这将为您提供一个名为together的新列,其中包括A1、B2等。

最好确定函数来自哪个包(tidyr?)。 - Frank
整洁宇宙(tidyverse)软件包 - Shirley
3
Tidyverse 是一组软件包。尝试使用 ?tidyverse::unite -- 这里面没有什么东西。对于那些只想解决手头问题而不想加载整个软件包的人来说,知道他们可以只加载 tidyr 就很好了。顺便说一句,我不是在批评答案,我已经点赞了,只是建议改进。 - Frank
感谢您的澄清,非常感谢。是的,正如您所指出的,unite函数实际上属于tidyr包。 - Shirley

4

这确实有点奇怪,但这也是应该发生的事情。 当您创建data.frame时,像您所做的那样,列letters被存储为factor。自然因素没有排序,因此当将as.numeric()应用于因子时,它返回因子的排序。例如:

> 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是因子df[, 1]的第一级别,因此当应用as.numeric时,A会转换为值1。这就是在调用paste(df[1, ])时发生的情况。由于列1和列2属于不同的类,paste首先将行1的两个元素转换为数字,然后再转换为字符。
当您想要连接两个列时,您需要先将第一行转换为字符:
df[, 1] <- as.character(df[, 1])
paste(df[1,], collapse = "")

正如@sebastian-c所指出的,您还可以在创建数据框时使用stringsAsFactors = FALSE,然后您就可以省略as.character()步骤。

1
如果你想开始,

请使用以下代码:

df <- data.frame(letters = LETTERS[1:5], numbers = 1:5, stringsAsFactors=TRUE)

..那么关于df$letters如何被任何给定函数解释,没有通用规则。对于建模函数来说,它是一个因子(factor),对于某些函数来说是字符(character),对于其他一些函数来说则是整数(integer)。即使是相同的函数,如paste,也可能根据使用方式而有不同的解释:

paste(df[1,], collapse="") # "11"
apply(df, 1, paste, collapse="") # "A1" "B2" "C3" "D4" "E5"

没有逻辑,除非你了解每个函数的内部工作原理,否则它可能是没有意义的。
当参数被转换为向量时,因素似乎会被转换为整数(正如您所知,数据框架是等长向量的列表,因此数据框架的第一行也是一个列表,当它被强制转换为向量时,就会发生类似这样的事情:)。
df[1,]
#    letters numbers
# 1       A       1
unlist(df[1,])
# letters numbers 
#  1       1 

我不知道 apply 如何实现它的功能(即,因子由字符值表示)-- 如果您感兴趣,请查看其源代码。然而,有用的是,您可以信任(在这个特定意义上)apply(在这个特定场合)。更一般地说,将每个数据存储在合理的格式中很有用,包括将字符串作为字符串存储,即使用 stringsAsFactors=FALSE
顺便说一句,每本介绍 R 的书都应该在副标题中包含这个想法。例如,我的退休计划是写一本名为“使用 R 进行数据挖掘之禅的(不太)温和介绍,以 stringsAsFactors=FALSE 为例”的书。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接