使用因子时,是否可能拥有比原始数据集更小的数据集?

3

我正在尝试减少数据集的内存占用,其中每列只有一小组因子(重复了很多次)。有没有更好的方法来最小化内存占用?作为对比,这是我仅使用因子时得到的结果:

library(pryr)

N <- 10 * 8
M <- 10

初始数据:

test <- data.frame(A = c(rep(strrep("A", M), N), rep(strrep("B", N), N)))
object_size(test)
# 1.95 kB

使用因子:

test2 <- as.factor(test$A)
object_size(test2)
# 1.33 kB

附言:我天真地认为他们用数字替换了字符串,但惊喜地发现test2test3要小。有没有人可以向我指出如何优化因子表示的材料?

test3 <- data.frame(A = c(rep("1", N), rep("2", N)))
object_size(test3)
# 1.82 kB

请注意:从技术上讲,这个问题是不在主题范围内的(https://stackoverflow.com/help/on-topic bullet 4),因为它只是要求推荐额外的阅读材料。您是否有特定的用例需要帮助,还是纯粹是信息性的? - Adam Sampson
3个回答

9
我很抱歉,差异很小。 原理本来很简单:在您的例子中,您只需存储2个字符串以及160个整数(仅为4个字节)。 但是R在内部以相同的方式存储字符。 每种现代语言都支持几乎无限长度的字符串。这就导致了一个问题,您无法像存储连续块一样存储字符串向量(或数组),因为任何元素都可以被重置为任意长度。因此,如果另一个值被分配给一个元素,并且该值稍微长一些,那么这意味着必须移动数组的其余部分。否则,操作系统/语言应该为每个字符串保留大量空间。 因此,字符串以方便的方式存储在内存中的任何位置,并且数组(或R中的向量)被存储为指向实际值所在位置的指针块。 在R的早期版本中,每个指针指向内存中的另一个位置,即使实际值相同。因此,在您的例子中,有160个指向160个内存位置的指针。但是现在已经改变了,现在实现为160个指向2个内存位置的指针。 可能会有一些小差异,主要是因为因子通常只支持2 ^ 31-1级别,这意味着32位整数足以存储它,而字符多使用64位指针。再说一遍,在因素中有更多的开销。 通常,如果您确实具有大量重复项,则使用因子可能会带来某些优势,但如果不是这种情况,则可能会损害您的内存使用情况。 您提供的示例无效,因为您将数据框与因子进行比较,而不是裸字符。 甚至更强:当我重现您的示例时,只有在将stringsAsFactors设置为FALSE时才能获得您的结果,因此您正在比较数据框中的因子和因子。 否则比较的结果要小得多:字符为1568,因子为1328。 而且那只有在您有很多相同值的情况下才有效,如果您查看此内容,您会发现因子可能会更大。
> object.size(factor(sample(letters)))
2224 bytes
> object.size(sample(letters))
1712 bytes

通常情况下,如果想让数据便于使用,没有真正有效的压缩方法,除非在存储时合理考虑需要保存哪些数据。


这是关于R内部工作原理的很好的解释。我相信最大因子数是2^31-1,除非R允许负/零因子索引。 - thc
我不知道底层表示是有符号整数还是无符号整数,但无论如何,我只是写了“它需要32位,并且具有比您可能需要的更多的可能级别”。但它很可能确实是 2^31-1 - Emil Bode
因子只是带有属性的INTSXP,而INTSXP只是一个“signed int”数组。您可以使用attributes(my_factor) <- NULL进行验证。 - thc
factor函数中,R使用levels <- levels[is.na(match(levels, exclude))],如果R对因子使用无符号整数,则此方法将无法正常工作。 - thc

2

我没有直接回答你的问题,但以下是来自Hadley Wickham的书《Advanced R》中的一些信息:

因子

属性的一个重要用途是定义因子。因子是一个向量,只能包含预定义的值,并用于存储分类数据。因子基于整数向量构建,使用两个属性:类别“factor”,使它们与常规整数向量不同,以及级别,定义允许的值集。

此外:

“虽然因子看起来(并且通常表现得)像字符向量,但它们实际上是整数。在将它们视为字符串时要小心。某些字符串方法(例如gsub()和grepl())会将因子强制转换为字符串,而其他方法(例如nchar())会抛出错误,还有一些方法(例如c())会使用底层整数值。因此,如果需要类似字符串的行为,则最好显式地将因子转换为字符向量。在早期版本的R中,使用因子而不是字符向量具有内存优势,但这种情况已不再存在。”


1

R中有一个叫做fst的包(R数据框架的闪电般快速序列化),你可以使用它来创建压缩的fst对象。关于如何使用它以及一个fst对象占用多少空间的详细说明可以在 fst-package手册中找到,但我会简要解释一下。首先,让我们将您的test数据框稍微扩大一点,如下所示:

library(pryr)
N <- 1000 * 8
M <- 100
test <- data.frame(A = c(rep(strrep("A", M), N), rep(strrep("B", N), N)))
object_size(test)
# 73.3 kB

现在,让我们将这个数据框转换为一个fst对象,如下所示:
install.packages("fst") #install the package
library(fst) #load the package
path <- paste0(tempfile(), ".fst") #create a temporary '.fst' file
write_fst(test, path) #write the dataframe into the '.fst' file
test2 <- fst(path) #load the data as an fst object
object_size(test2)
# 2.14 kB

创建的 .fst 文件所占磁盘空间为 434 字节。您可以像处理普通数据框一样处理 test2(据我尝试)。希望这有所帮助。

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