这篇文章继续我的系列文章,介绍常见的数据科学和机器学习任务的R和Python代码之间的翻译。为那些像我这样经常为不同任务在两种语言之间切换的人提供一份记录在案的小抄。
早在2020年底,我就写了这个系列的第一篇文章,充满乐观地认为它将拉开关于这个主题的整个系列的频繁文章。然而,其他项目占据了优先地位,就像它们经常做的那样。现在,我带着新的热情回来了,想在今年做更多的博客,写更多关于R和Python的文章。
让我们先把设定说出来:所有的代码都可以通过Rstudio IDE(1.2版以上)在Rmarkdown中复制。Python是通过reticulate集成的;请看我关于reticulate的介绍文章来开始使用。这些是我们的R和Python库。
# R
libs <- c('dplyr', 'stringr', # wrangling
'palmerpenguins', 'gt', # data, table styling
'vroom', 'readr', # read & write data
'tidyr', 'purrr', # wrangle & iterate
'fs', # file system
'reticulate') # python support
invisible(lapply(libs, library, character.only = TRUE))
use_python("/usr/bin/python3")
df_orig <- penguins %>%
mutate_if(is.integer, as.double) %>%
select(-contains("length"), -contains("depth"))
# Python
import pandas as pd
import glob
pd.set_option('display.max_columns', None)
我们将再次使用同名R包中的Palmer Penguins数据集。出于可读性的考虑,我将使用一个较小的列的子集。
这个数据集包含了3种企鹅的测量和特征,即。
df_orig %>%
count(species) %>%
gt()
| 物种 | n |
|---|---|
| 阿德利企鹅 | 152 |
| 钦斯特拉 | 68 |
| 玄鸟 | 124 |
在这个练习中,我们将把数据框按物种拆成不同的文件,然后再把它重新组合起来。
简而言之。编写多个csv文件
不过,拆开的部分并不是这篇文章的重点。我是从这个stackoverflow的帖子中借用的。为了使这个练习的其余部分更加紧凑,我们将只把每个物种的1行写到csv文件中。
# R
df_orig %>%
group_by(species) %>%
slice_head(n = 1) %>%
ungroup() %>%
nest(data = c(-species)) %>%
pwalk(function(species, data) write_csv(data, file.path(".", str_c(species, "_penguins.csv"))))
为了简洁起见,这段代码将保持某种程度的黑匣子;否则这篇文章就会膨胀成更长的东西。你可以把它看作是未来内容的预演;你也可以按原样使用或自己探索。
一些简单的说明:slice_head 是dplyr 包的一个新成员--由以前的slice 演变而来--它所做的和你所期望的差不多:切掉前n个条目(这里按组)。在nest 中,我们有一个tidyr 工具,它将选择列捆绑在一起,变成一个列表列(这里除了species 之外的所有内容)。最后,purrr 包中的函数pwalk 在一个列表上进行迭代,这里用自定义的名字写出数据框架的不同部分。
目前,purrr 仍然是我在 tidyverse 中的一个弱点,我计划在未来的文章中重新讨论它。现在,我们来看看本集的主要内容。
读取单个csv文件
在R语言中,读取csv文件的标准工具是readr::read_csv 。然而,我更喜欢vroom包中的vroom 函数。正如其拟声词的名字所暗示的那样,这个包的速度非常快。比data.table::fread 更快,在vroom 出现之前,我一直喜欢它。
在Python / pandas的世界里,选择更为有限,我们使用pd.read_csv 。
# R
single_file <- vroom("Adelie_penguins.csv", col_types = cols())
single_file %>%
gt()
| 岛屿 | 体重(body_mass_g | 性别 | 年 |
|---|---|---|---|
| Torgersen | 3750 | 男性 | 2007 |
# Python
single_file = pd.read_csv("Adelie_penguins.csv")
single_file
## island body_mass_g sex year
## 0 Torgersen 3750 male 2007
除了pandas添加了它的特征索引外,我们得到的数据框架与结果相同。到目前为止,如此简单明了。
读取多个csv文件
但我在这里真正想写的是读取多个csv文件。如果你在一个目录中包含许多具有相同模式的csv文件(也许来自一个自动化的日常管道),这些方法将非常方便。
在我们读取这些文件之前,我们首先要找到它们。在R中,我们使用fs 包中的dir_ls 工具,在当前目录(.)中搜索一个全局模式("*penguins.csv")。
# R
(files <- fs::dir_ls(".", glob = "*penguins.csv"))
## Adelie_penguins.csv Chinstrap_penguins.csv Gentoo_penguins.csv
这就给了我们3个文件,我们把企鹅的数据分成了3个文件。现在我们可以把这些文件的矢量直接输入到vroom (或read_csv )。
# R
df <- vroom(files, col_types = cols(), id = "name")
df %>%
gt()
| 名称 | 岛屿 | 体重_g | 性别 | 年份 |
|---|---|---|---|---|
| Adelie_penguins.csv | Torgersen | 3750 | 男性 | 2007 |
| Chinstrap_penguins.csv | 梦想 | 3500 | 雌性 | 2007 |
| Gentoo_penguins.csv | 比斯克 | 4500 | 雌性 | 2007 |
正如你所看到的,这里的id 参数允许我们添加一个漂亮的name 列,它保存了文件名。由于我们用企鹅的种类来命名我们的文件,这使我们能够在我们的表中得到该种类的信息。其他的应用是让文件名带有时间戳或其他关于你的上游管道的信息。如果你能控制管道输出的命名模式,或者能贿赂有此能力的人,那么你就可以通过在这些文件名中加入有用的信息,使你的生活变得更容易。
在Python中,我们使用glob 来获取文件名。
files_py = glob.glob("*penguins.csv")
files_py
## ['Adelie_penguins.csv', 'Chinstrap_penguins.csv', 'Gentoo_penguins.csv']
然后像这样阅读。pd.concat 所做的是将一个数据框的列表绑定在一起,成为一个单一的数据框。这个列表来自于Python最喜欢的方法之一:列表理解。关于列表理解的灵活性和优雅性,有很多话要说,但归根结底,它们基本上就是一个输出列表的单行for循环。所以在这里,我们循环浏览了3个文件名,并将它们读成一个数据帧的列表,送入pd.concat 。
df_py = pd.concat((pd.read_csv(f) for f in files_py))
df_py
## island body_mass_g sex year
## 0 Torgersen 3750 male 2007
## 0 Dream 3500 female 2007
## 0 Biscoe 4500 female 2007
但这仍然缺少文件名。幸运的是,我们可以通过灵巧的赋值函数获得这些信息。
df_py = pd.concat((pd.read_csv(f).assign(name = f) for f in files_py))
df_py
## island body_mass_g sex year name
## 0 Torgersen 3750 male 2007 Adelie_penguins.csv
## 0 Dream 3500 female 2007 Chinstrap_penguins.csv
## 0 Biscoe 4500 female 2007 Gentoo_penguins.csv
很好!现在我们得到了文件名中包含的信息,在我们的数据框架中很方便。
就像我之前写的那样,在野外,经常会发生文件名包含重要信息的情况。有时是如此,以至于我们想进一步解析这一特征。
将一列分离成多列
在R中,为了将一列变成多列,我们有来自tidyr 包的separate 函数。你把你想分配给新列的名字和分隔符一起交给它。(注意,这里我们需要转义点,这样它就不会被误解为一个正则表达式)。
df %>%
separate(name, into = c("name", "filetype"), sep = "\\.") %>%
gt()
| 名称 | 文件类型 | 岛 | 体重_g | 性别 | 年 |
|---|---|---|---|---|---|
| 阿德利企鹅 | csv | Torgersen | 3750 | 男性 | 2007 |
| 秦岭_企鹅 | csv | 梦想 | 3500 | 雌性 | 2007 |
| Gentoo_penguins | csv | 比斯克 | 4500 | 雌性 | 2007 |
当然,我们还可以用同样的方法进一步拆开name 列。
df %>%
separate(name, into = c("name", "filetype"), sep = "\\.") %>%
separate(name, into = c("species", "animal"), sep = "_") %>%
gt()
| 物种 | 动物 | 文件类型 | 岛屿 | 体重_g | 性别 | 年 |
|---|---|---|---|---|---|---|
| 阿德利企鹅 | 企鹅 | csv | Torgersen | 3750 | 雄性 | 2007 |
| 金丝雀 | 企鹅 | csv | 梦想 | 3500 | 雌性 | 2007 |
| 玄鸟 | 企鹅 | csv | 比斯克 | 4500 | 雌性 | 2007 |
在Python中,pandas没有专门的操作来分割列。相反,这可以通过使用字符串函数来完成,Python有一套与R相当的字符串函数。对于pandas数据框架,我们需要首先添加.str 方法,以表明后面有一个字符串操作,然后将其矢量化到整个列中。在这里,我们在上面相同的分隔符上使用了两个split 。expand 参数负责将其分离成多列(否则结果将是一个包含列表特征的单列)。name 之后,我们需要drop 列,以获得与R操作相同的结果。
# Python
df_py = pd.concat((pd.read_csv(f).assign(name = f) for f in files_py))
df_py[['name', 'filetype']] = df_py['name'].str.split('.', expand=True)
df_py[['species', 'animal']] = df_py['name'].str.split('_', expand=True)
df_py = df_py.drop('name', axis = 'columns')
df_py
## island body_mass_g sex year filetype species animal
## 0 Torgersen 3750 male 2007 csv Adelie penguins
## 0 Dream 3500 female 2007 csv Chinstrap penguins
## 0 Biscoe 4500 female 2007 csv Gentoo penguins
注意,与R的separate 相比,这个过程需要中间步骤,而且不能连锁进行。
将多列合并为一列
正如你可以从我的标题中猜到的,我更喜欢tidyverse处理这些操作的方式。为了将被拆开的东西重新连接起来,我们使用tidyr 函数unite 。我们从分离的版本开始。
df %>%
separate(name, into = c("name", "filetype"), sep = "\\.") %>%
separate(name, into = c("species", "animal"), sep = "_") %>%
gt()
| 物种 | 动物 | 文件类型 | 岛屿 | 体重_g | 性别 | 年 |
|---|---|---|---|---|---|---|
| 阿德利企鹅 | 企鹅 | csv | Torgersen | 3750 | 雄性 | 2007 |
| 金丝雀 | 企鹅 | csv | 梦想 | 3500 | 雌性 | 2007 |
| 玄鸟 | 企鹅 | csv | 比斯克 | 4500 | 雌性 | 2007 |
然后绕了一圈,又一口气把它装了回去。在这2个分离的步骤之后,正在进行两个联合的步骤,将它们倒过来。对于unite ,你需要指定你想粘贴在一起的列,然后指定新列的名称,以及要使用的分隔符。
df %>%
separate(name, into = c("name", "filetype"), sep = "\\.") %>%
separate(name, into = c("species", "animal"), sep = "_") %>%
unite(species, animal, col = "name", sep = "_") %>%
unite(name, filetype, col = "name", sep = ".") %>%
gt()
| 名称 | 岛 | 体重_g | 性别 | 年份 |
|---|---|---|---|---|
| Adelie_penguins.csv | Torgersen | 3750 | 男性 | 2007 |
| Chinstrap_penguins.csv | 梦想 | 3500 | 雌性 | 2007 |
| Gentoo_penguins.csv | 比斯克 | 4500 | 雌性 | 2007 |
在这里,在联合过程中,额外的列被自动删除(即species,animal,filetype ),但是你可以通过指定remove = FALSE 来改变这种行为。这同样适用于separate 。
pandas表已经被分开了,这是最难的部分。合并它们要直观得多;利用 Python 的风格来实现对复杂对象的简单操作。字符串可以使用+ 操作符进行连接,常量可以自动扩展为矢量尺寸。因此你可以像对待零维变量一样对待这些列。
我们再次删除多余的列,然后就可以了:我们又回到了开始的地方。在这种情况下,这是一件好事。
df_py['name'] = df_py['species'] + "_" + df_py['animal'] + "." + df_py['filetype']
df_py = df_py.drop(['species', 'animal', 'filetype'], axis = 'columns')
df_py
## island body_mass_g sex year name
## 0 Torgersen 3750 male 2007 Adelie_penguins.csv
## 0 Dream 3500 female 2007 Chinstrap_penguins.csv
## 0 Biscoe 4500 female 2007 Gentoo_penguins.csv
今天就到这里吧。回顾一下:我们看了一下如何加载一个csv文件,然后用R和Python加载多个csv文件。然后我们用这些数据框架来练习列的分离和合并。
因此,我们在我们的双语课程中增加了一些相当重要的进口数据分析工具。(看到我所做的了吗?)更多的罗塞塔石碑内容将很快到来。