关注我们并在后台回复 “进群”,即可加入农心生信工作室学习交流群,群内不定时分享源代码及示例文件,并在线交流答疑。我们在农心生信,等你!
由于微信改版,乱序推送,很多朋友反映收不到公众号推文。快跟着图片步骤,将农心生信公众号设为星标,不错过每一条精彩内容!!
本公众号已开通留言功能,欢迎大家直接在文末留言交流!
写在前面
当我们在处理任何组学的fasta序列数据时,对不同样本运行相同的软件后可能会生成相同的输出文件,这些根据样本名命名的输出文件有一个特点:具有相同的行名和列名但数值不同。如果样本量很大且我们可能只需要不同样本的某一列信息,这时就需要对这些文件的特定列进行提取并在列名上添加样本名target,最终将不同样本输出文档的特定列合并到一个新表中。
例如,有四个样本的输出文档sample1.txt, sample2.txt, sample3.txt, sample4.txt
以及一个储存样本名id的list文件sampleid.txt
我们发现,这四个文件的第一列,也就是名为name的列都相同,那么,如何根据相同的name列合并四个文件呢?
这里有多种方式实现这个需求。
单行AWK
一行命令实现:
awk -F '\t' '{print $1}' sample1.txt > first_column.txt && while read -r i; do sed “s/number/${i}_number/g” ${i}.txt > ${i}.txt2 && awk -F '\t' '{print $2}' ${i}.txt2 > ${i}_column2.txt; done < sampleid.txt && paste first_column.txt $(awk '{print $0"_column2.txt"}' sampleid.txt) > merge_column.txt
生成的文件merge_column.txt
最后,删除生成的中间文件
while read -r i; do rm ${i}.txt2 ${i}_column2.txt; done < sampleid.txt
这里提供给大家的是结合了awk和循环实现合并行列名相同的多样本输出文档。
该命令分为三部分:
第一部分,提取任意一个表的第一列
第二部分,处理每一个文件,为特定列加样本target后提取特定列
第三部分,合并行名(即第一列)和各列
第二部分的功能还可以替换成使用xargs,或者使用多样本并行软件parallel【软件教程 | 一文学会多任务并行神器parallel】
awk -F '\t' '{print $1}' sample1.txt > first_column.txt && cat sampleid.txt | xargs -I {} sh -c 'sed "s/number/{}_number/g" {}.txt | awk -F "\t" "{print \$2}" > {}_column2.txt' && paste first_column.txt $(awk '{print $0"_column2.txt"}' sampleid.txt) > merge_column.txt
awk -F '\t' '{print $1}' sample1.txt > first_column.txt && cat sampleid.txt | parallel 'sed "s/number/{}/g" {}.txt | awk -F "\t" "{print \$2}" > {}_column2.txt' && paste first_column.txt $(awk '{print $0"_column2.txt"}' sampleid.txt) > merge_column.txt
base-R
# 读取样本ID
sample_ids <- readLines("sampleid.txt")
# 初始化一个空列表来存储数据框
data_list <-list()
# 遍历每个样本文件
for(sample_id in sample_ids){
# 构造文件名
file_name <- paste0(sample_id,".txt")
# 从文件中读取数据
data <- read.table(file_name, header =TRUE)
# 提取'name'列和第一列数据
data_subset <- data[,c("name", setdiff(names(data),"name")[1])]
# 重命名提取的数据列,以样本ID加上"_target"作为列名
colnames(data_subset)[2]<- paste0(sample_id,"_target")
# 添加到列表中
data_list[[sample_id]]<- data_subset
}
# 通过'name'列合并所有数据框
result <-Reduce(function(x, y) merge(x, y,by="name",all=TRUE), data_list)
# 将结果写入新文件
write.table(result,"merge_column.txt", sep ="\t",quote=FALSE, row.names =FALSE)
tidy-R
这里与base-R的区别就在于使用了tidyverse包,对数据的处理都采用了tidyverse内的函数:
library(tidyverse)
# 读取样本ID
sample_ids <- readLines("sampleid.txt")
# 初始化一个空列表来存储数据框
data_list <-list()
# 遍历每个样本文件
for(sample_id in sample_ids){
# 构造文件名
file_name <- paste0(sample_id,".txt")
# 从文件中读取TSV格式的数据
data <- read_tsv(file_name)
# 提取'name'列和以除'name'外的第一列数据列名开头的列,
# 然后将提取的数据列重命名为“样本ID_target”
data_subset <- data %>%
select(name, target = starts_with(setdiff(names(data),"name")[1]))%>%
rename(!!paste0(sample_id,"_target"):= target)
# 添加到列表中
data_list[[sample_id]]<- data_subset
}
# 通过'name'列将所有数据框进行全外连接合并
result <- reduce(data_list, full_join,by="name")
# 将结果写入新的TSV文件
write_tsv(result,"merge_column.txt")
python
在使用python实现需求时,我们调用了pandas库,来帮助我们更好地处理数据:
import pandas as pd
# 读取样本ID
withopen('sampleid.txt')as f:
sample_ids = f.read().splitlines()
# 初始化一个空的数据框
result =None
# 遍历每个样本文件
for sample_id in sample_ids:
file_name =f"{sample_id}.txt"
# 从文件中读取以制表符分隔的数据
data = pd.read_csv(file_name, sep="\t")
# 提取'name'列和第一列数据
target_column = data.columns[1]
data_subset = data[['name', target_column]].copy()
# 重命名提取的数据列,将第一列数据命名为“样本ID_target”
data_subset.columns =['name',f"{sample_id}_target"]
# 与结果数据框合并
if result isNone:
result = data_subset
else:
# 通过'name'列以外连接的方式合并数据框
result = pd.merge(result, data_subset, on='name', how='outer')
# 将结果写入新的文件,使用制表符作为分隔符,不包含索引
result.to_csv("merge_column.txt", sep="\t", index=False)
关于这个需求实现,大家还有什么另辟蹊径的解决方式,欢迎留言讨论分享!
写在最后
当然,这其实只是一个很简单的需求,但我们在这篇推文中却使用了多种方法来实现它,只是想说明,不管用什么方法,只要殊途同归,完成我们的需求,都是好方法。这好比大家在学习生物信息学的过程中,有时会纠结到底该优先学习什么编程语言;但在我们看来,这并不是一个需要太过纠结的问题,不论python、R甚至是perl,学起来、用起来才是关键,试图去掌握一门语言,而不仅仅是了解几门语言。
END
编辑 | Narcissus
供稿 | littleslug
审核 | 农心生信工作室