R语言dataframe和pandas的比较

dataframe是统计学研究中经常使用的一种数据格式,最早在R语言上实现。python作为后起之秀,通过pandas这一模块也实现了对dataframe的支持。那么,这两种不同编程语言的dataframe又有什么区别呢?二者之间能否实现数据互通呢?本文将回答这些问题。

从R语言历史说起

S语言,一种用于统计的编程语言,由贝尔实验室的约翰·钱伯斯 、瑞克·贝克尔(Rick Becker)与艾伦·威尔克斯(Allan Wilks)共同研发,在1975年至1976年间在贝尔实验室被开发出来。

在那个年代,最主要的统计运算程序都是直接调用Fortran的子程序。但是S语言采用了高度互动式的方法来实现,因此极其先进,并被统计学工作者广泛采用。

S语言的后继者包括S-PLUS和R,然而S-PLUS是商业软件,因此流行并不广。更为人所熟知的是R语言。

1995年,新西兰Auckland大学的Robert Gentleman 和 Ross Ihaka(名字前缀均为R) 及其他志愿人员基于S语言的源代码开发了R语言系统。R语言增加了Scheme语言中词法作用域这一机制,使程序员得以将代码中某一对象的适用范围限制到一小段代码之中。在统计学家马丁·梅克勒的建议下,R语言成为GNU公共许可证下的一款免费开源软件。1997年4月,R综合文件网(CRAN)正式上线,其作为R语言各种软件包的仓库被广泛使用,地位相当于python的pypi。

因为S语言的血缘,R语言原生拥有一些适合统计学编程的面向对象数据结构,例如data.frame (数据框)这一数据类型。

原生R语言的语法依然有些晦涩,于是有能人强者开发了tidyverse这一软件包集合,包括ggplot2(绘图)、dplyr(数据过滤和操作)、stringr(字符串处理)、readr(数据导入和导出)等多个著名工具包,它们“共享一个基本的设计理念、语法和数据结构”,也因此被广泛使用,现在已渐渐有了取代了R-base中一些基础函数的趋势。

bioconductor 是一个生信领域开源软件包的分发平台,大量生物学数据的处理工具托管于此。bioconductor上的软件包大都服务于R语言,使用到了许多R的功能,bioconductor包管理器也运行在R语言上,但bioconductor与CRAN没有任何关系,前者使用biocmanager::install 进行包管理,后者使用 install.packages()进行包管理。

R语言的dataframe

如果是从其他编程语言转到R语言的初学者,对dataframe这一数据类型会感到有些莫名其妙。它既不像python的向量和矩阵那样,可以抽象为一维和二维的数组结构,也不像C++那种支持自定义属性和方法的“对象”。然而,经过一段时间的使用,其实会发现,dataframe是一种强大的数据结构。

如下所示,我们使用head() 函数打印一个dataframe的内容,可以看出这是一种以数据库表或Excel表格形式存储数据的结构。和数组/矩阵不同,dataframe支持按列名访问数据,这很适合处理一些统计表格中的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> data(iris) # 加载iris数据集
> head(iris) # 打印iris数据集的前6行内容
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
> head(iris["Species"]) # 访问`Species` 这一列的信息
Species
1 setosa
2 setosa
3 setosa
4 setosa
5 setosa
6 setosa
>

dataframe也支持按行号或列号进行索引。如下代码是两个例子,分别访问数据框的第一个元素和打印一定范围内的元素。

1
2
3
4
5
6
7
> iris[1,1]
[1] 5.1
> iris[1:3,1:5]
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa

与dataframe相辅相成的还有R语言内置的apply函数,它可以对dataframe进行高效的行操作和列操作。R语言还有一系列readwrite函数,如read.csvwrite.csv,支持从表格文件中导入数据,或者导出数据到表格文件当中。

Python对dataframe格式的支持

目前我们已经知道,Python的Pandas包也支持dataframe格式,二者在某些细节方面有区别,但设计思路大体上相同。或者说,pandas从一开始就是Python社区为了对标R语言的dataframe而写出来的模块。从R markdown核心开发人员、R语言大佬谢益辉的博客中可以看出这一点:

上回是讲 R 的各种怪癖,经过这五个月,我觉得 Python 有一统江湖的野心。过去我们总说统计是 R 的强项,别的语言要重写 R 的四千个包根本不可能,看 pandas 费了多大劲才实现 R/S 几十年前就实现的数据框结构,蟒蛇社区要重写个 ggplot2 有希望吗?多数人可能不会选择重造轮子,可是我渐渐发现蟒蛇社区真的是鸡血太充足了,真有人愿意把统计的东西一项一项重写出来。上次我在微博上说有人用 IPython notebook 展示了线性模型的设计阵,便是一个一统统计江湖的象征,现在又有人开始用 IPython 写贝叶斯 / MCMC 的书。小众的 R 社区,会不会被蟒蛇吞掉呢?

——《千年等一回》谢益辉 2013-02-27

pandas实现了dataframe的各种操作,包括按列的数据选择器、数据选取和修改、从文件读取数据、导出数据到文件等功能。

image.png

如上图,pandas的官方文档甚至贴心的给出了pandas与其他工具的比较,以方便大家从其他工具迁移到pandas。

pandas vs R dataframe

终于到了重点,pandas和R dataframe这两个工具的区别是什么呢?

下面这一段的内容主要参考pandas官方文档 。表格里面列出了dataframe的常见操作在两个工具中的区别:

1. 查询、过滤和抽样

R pandas 备注
dim(df) df.shape 输出dataframe的数据维度
head(df) df.head() 输出dataframe前几行数据
slice(df, 1:10) df.iloc[:9] 按行号或列号索引对数据进行切片。在R中,这一功能需要dplyr包的支持
filter(df, col1 == 1, col2 == 1) df.query('col1 == 1 & col2 == 1') 按条件进行过滤。在R中,这一功能需要dplyr包的支持
df[df$col1 == 1 & df$col2 == 1,] df[(df.col1 == 1) & (df.col2 == 1)] 按条件进行过滤
select(df, col1, col2) df[['col1', 'col2']] 按列名进行数据选取。在R中,这一功能需要dplyr包的支持
select(df, col1:col3) df.loc[:, 'col1':'col3'] 按列名进行数据选取(选择多个列)。在R中,这一功能需要dplyr包的支持
select(df, -(col1:col3)) df.drop(cols_to_drop, axis=1) 按列名进行数据过滤(排除掉指定列)。在R中,这一功能需要dplyr包的支持。在pandas中,需要额外的代码提取出所有要排除的列
distinct(select(df, col1)) df[['col1']].drop_duplicates() 选择一列中不同的数据
distinct(select(df, col1, col2)) df[['col1', 'col2']].drop_duplicates() 选择两列中不同的数据
sample_n(df, 10) df.sample(n=10) 按数量抽样
sample_frac(df, 0.01) df.sample(frac=0.01) 按比例抽样

2. 排序

R pandas
arrange(df, col1, col2) df.sort_values(['col1', 'col2'])
arrange(df, desc(col1)) df.sort_values('col1', ascending=False)

3. 变换

R pandas
select(df, col_one = col1) df.rename(columns={'col1': 'col_one'})['col_one']
rename(df, col_one = col1) df.rename(columns={'col1': 'col_one'})
mutate(df, c=a-b) df.assign(c=df['a']-df['b'])

4. 分组与总结

R pandas
summary(df) df.describe()
gdf <- group_by(df, col1) gdf = df.groupby('col1')
summarise(gdf, avg=mean(col1, na.rm=TRUE)) df.groupby('col1').agg({'col1': 'mean'})
summarise(gdf, total=sum(col1)) df.groupby('col1').sum()

由于R语言对象系统的特点,上述比较中用到的R语言的函数如select(),mutate(),summary() 等都可以直接调用;但在python中,这些函数属于pandas对象的方法,因此需要用“对象名+方法名”的方法来调用(例如 df.head() 就是调用df这一pandas对象的head()方法。但总体上看,两种语言实现的dataframe操作在很多地方都是一致的。

R dataframe和pandas dataframe的相互转换

有些时候我们可能有跨编程语言操作的需要,例如在R语言中使用某个bioconductor上的包进行数据预处理,随后用python上的某些工具进行建模和可视化。因此,将dataframe对象在两种语言之间相互转换也是有必要的。

最简单粗暴的转换方法是以文本文件(.csv或.txt)为中介的。具体方法如下:

R pandas
导出到文本文件 write.table()write.csv() pandas.to_table()pandas.to_csv()
从文本文件导入 read.table()read.csv() pandas.read_table()pandas.read_csv()

但是,当数据量过大时,这样的导入导出操作极其耗时,且会产生巨大的文本文件。如果能以一些中间格式(如.RData)为媒介进行数据交换,则会更方便一点。

.RData文件是R语言的序列化文件格式。所谓序列化 (Serialization)就是将对象的状态信息转换为可以存储或传输的形式的过程,譬如说将一个dataframe的二进制数据流直接存储到文件当中(而不是转换为csv这种文本文件)。R语言提供了save()save.image()两个函数用于将数据对象存储到 .RData文件,提供了load()函数用于从 .RData文件中读取对象。使用.RData文件存储速度更快,且更节省空间。

那么python有没有可能去读取一个R语言的.RData文件呢?答案是可以的。有一个python模块叫做rpy2,可以实现python中对R的调用,以及二者之间的数据结构互转。使用pip install rpy2指令即可进行安装

“攻略” : “R 语言格式数据导入python进行分析” - Psion03的文章 - 知乎

关于R dataframe转pandas dataframe,可以参考下面的文档说明

https://rpy2.github.io/doc/v3.5.x/html/generated_rst/pandas.html#from-r-to-pandas

以一个实例来说明。我们将R语言的示例数据集iris导入到.RData里面,随后再用python读取这个数据集。首先,使用下面的代码导出iris

1
2
data(iris)
save(iris,file="iris.RData") # 现在iris.RData文件里面是iris这个数据框

然后在同一文件夹下,使用下面的python代码导入iris

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import numpy as np
import pandas as pd
import os
os.environ["R_HOME"]='D:\\R\\R-4.3.1' # 此处把路径设置为你的电脑上的R可执行文件的位置
# R_HOME环境变量必须设置一下,否则下一行 `import rpy2` 会出错
# 下面导入一些必须的rpy2模块
import rpy2.robjects as ro
from rpy2.robjects.packages import importr
from rpy2.robjects import pandas2ri
# import R's "base" package
base = importr('base')
# import R's "utils" package
utils = importr('utils')
# 这里我们尝试将 `matrix1` 的四个dataframe导入进来
# 修正:只能导入一个dataframe。另外几个dataframe之后慢慢处理。

# 下面定义Rcode。rpy2需要调用R解释器,运行这些Rcode完成一些操作。
rcodes = """
load("iris.RData") #读取前面保存的RData文件
return(iris) #将iris这个数据框作为Rcode运行的返回值
"""

# 运行R code,读取并导出iris
iris=ro.r(rcodes.strip())

# 此时的iris虽然导入到python中了,然而对象类型依然是R/rpy2 DataFrame,无法直接操作。
# 需要下面的代码将其转变为可操作的pandas数据框对象。
# 将R dataframe转码为pandas dataframe
with (ro.default_converter + pandas2ri.converter).context():
iris_df = ro.conversion.get_conversion().rpy2py(iris)
iris_df # 打印一下看看

运行结果如下:

image.png

如图,通过我们的一番操作,成功实现了将R语言的dataframe导入python中的操作。