R语言中长格式数据结构占用更多内存的原因解析
这问题问得太到位了!我来帮你一步步拆解背后的原因:
不小心混合数据类型导致列被强制转换
看你的代码:mtcars %>% rownames_to_column() %>% gather()——这里你没指定要保留rowname列作为分组列,所以gather会把所有列(包括字符型的rowname和数值型的mtcars原列)都转成key-value对。这就导致value列不得不变成字符型(因为要同时容纳字符串形式的行名和数值),而字符型在R里的存储开销远大于数值型(双精度数值仅占8字节,字符型每个元素还要额外存储指针和字符串内容)。
如果你改成只gather数值型列,内存会立刻降下来:test_fixed <- mtcars %>% rownames_to_column() %>% gather(variable, value, -rowname) object.size(test_fixed) # 会比你的test小很多,但还是比mtcars大行数暴增带来的基础开销
原mtcars是32行×11列,而你的test是32×12=384行(把12列都转成了长格式),行数是原来的12倍!哪怕每行只多一点点存储开销(比如类型标记、指针等),累积起来也很可观。宽格式里这些开销是按列来算的,长格式则要按行重复计算。字符型列的重复存储成本
长格式里的key列存的是原列名(比如mpg、cyl),这些字符串会重复出现32次(每个列名对应32行数据)。虽然R会对重复字符串做共享存储优化,但每个key元素还是要占用一个指针(8字节),再加上字符串本身的存储,这部分开销在宽格式里是没有的——宽格式里列名只存一次,作为data.frame的属性,而非每行都存储。Tibble的额外元数据开销
你的test是tibble类型,而mtcars是普通的data.frame。Tibble会存储更多元数据(比如列的类型信息、打印格式设置等),这也会增加少量内存占用,不过这不是主要原因。
简单来说,宽格式在存储同一份数据时,利用了列名只存一次、统一数值类型的优势,而长格式为了结构灵活,不得不付出重复存储和类型转换的内存代价。
内容的提问来源于stack exchange,提问作者Jacob Nelson

