【问题标题】:Removing duplicate rows in .tsv while keeping some of the data (bash, perl)删除 .tsv 中的重复行,同时保留一些数据(bash、perl)
【发布时间】:2020-06-30 12:26:43
【问题描述】:

我有许多大型 .tsv 文件,如下所示:

rownbr     pos      pvalue   percentage    samplename
1      chr1_12000    0.05       5.6            S1
1      chr1_12500    0.04       15.9           S1
3      chr1_12570    0.9        45.3           S2
2      chr1_12500    0.03       13.8           S3

我想根据 pos 列删除重复的行,同时仍保留列 35 两行的值以便输出看起来像这样:

rownbr     pos      pvalue   percentage    samplename
1      chr1_12000    0.05       5.6            S1
1      chr1_12500    0.04,0.03  15.9           S1,S3
3      chr1_12570    0.9        45.3           S2

我的想法是首先使用 shell sort 函数对 .tsv 文件进行排序:

sort -k 2,2 *.tsv

然后编写一个脚本,将每一行与下一行进行比较。 如果 pos 列中的字符串对于两行都是相同的,那么它将连接 row n+1 中的第 3 列和第 5 列的值到 中的值>第n行。 但是我不知道该怎么做。

我熟悉 awk/sed/grep/bash,但也有一些(有限的)perl 基础知识。

感谢您的帮助!

【问题讨论】:

  • 在预期输出中:为什么 pos=12500 15.9 而不是 13.8 的百分比列?同样,为什么rownbr 列是1 而不是2。对于给定的pos,是否可以有超过 2 个重复行?

标签: bash perl duplicates


【解决方案1】:

这是一个在 Perl 中如何处理它的示例:

use feature qw(say);
use strict;
use warnings;

my $fn = 'file1.tsv';
open ( my $fh, '<', $fn ) or die "Could not open file '$fn': $!";
my $header = <$fh>;
my @pos;
my %info;
while( my $line = <$fh> ) {
    chomp $line;
    my ($nbr, $pos, $pvalue, $percentage, $samplename) = split /\t/, $line;
    if ( !exists $info{$pos} ) {
        $info{$pos} = {
            nbr        => $nbr,
            pvalue     => [$pvalue],
            percentage => $percentage,
            samplename => [$samplename],
        };
        push @pos, $pos;
    }
    else {
        push @{$info{$pos}{pvalue}}, $pvalue; 
        push @{$info{$pos}{samplename}}, $samplename; 
    }
}    
close $fh;

print $header;
for my $pos (@pos) {
    my $data = $info{$pos};
    say join "\t", $data->{nbr}, $pos,
      (join ",", @{$data->{pvalue}}), $data->{percentage},
      (join ",", @{$data->{samplename}});
}

输出

rownbr     pos      pvalue   percentage    samplename
1   chr1_12000  0.05    5.6 S1
1   chr1_12500  0.04,0.03   15.9    S1,S3
3   chr1_12570  0.9 45.3    S2

【讨论】:

  • 这很好用,谢谢@HåkonHægland!为了回答您上面的问题,我选择只保留第一次出现的百分比数字以获得所需的输出。这样做的主要目的是表明我不想保留所有列的值,但就是这样。我应该说得更清楚。是的,给定的 pos 可以有超过 2 行重复的行,但是无论有多少重复,您的代码都可以工作,所以这很棒!
【解决方案2】:

文件“我的脚本”:

#! /usr/bin/env bash

file="$1"

result="$(tr -s '\t' < "${file}"  | tail -n +2 |
        awk -F'\t' -v OFS='\t' '

        $0 == "" {
            next
        }

        # MAIN 
        {
            if (col3[$2] == "") {
                col1[$2] = $1
                col3[$2] = $3
                col4[$2] = $4   
                col5[$2] = $5
            } else {
                col3[$2] = col3[$2]","$3
                col5[$2] = col5[$2]","$5
            }
        }

        END {

            for (pos in col1) {
                print col1[pos], pos, col3[pos], col4[pos], col5[pos]
            }
        }
        ' | sort -k 2,2 )"

first_line="$(head -n 1 "${file}")"
echo "${first_line}"
echo "${result}"

运行如下:

bash myscript <your tsv file>

它将结果写入标准输出。

【讨论】:

  • 谢谢@Mihir!这适用于我提供的示例数据框。我仍然会将 Hakon 的 perl 答案作为主要答案,因为我发现它更容易适应更复杂的数据帧。
【解决方案3】:

使用GNU datamashawk 的组合来获得所需的列:

$ datamash --header-in -sf -g2 collapse 3,5 < input.tsv | \
  awk 'BEGIN { FS=OFS="\t"; print "rownbr\tpos\tpvalue\tpercentage\tsamplename" }
       { print $1, $2, $6, $4, $7 }'
rownbr  pos pvalue  percentage  samplename
1   chr1_12000  0.05    5.6 S1
1   chr1_12500  0.04,0.03   15.9    S1,S3
3   chr1_12570  0.9 45.3    S2

忽略文件中的标题行(--header-in),将记录分组到第二列(-g2),根据该列排序(-s),输出整行(-f)除了给定的操作之外,对于第 3 列和第 5 列,将组的所有行折叠到单个 CSV 条目中。 Tnen 使用awk 将所需的列按正确的顺序排列。

【讨论】:

  • 嗨@Shawn,感谢您的回答。我存储数据的计算机集群没有安装 GNU datamash,并且由于我无权在那里安装任何软件包,因此 HåkonHægland 的 perl 答案在短期内对我来说效果更好。然而,这看起来很有趣,也很简单。我从未使用过 GNU 命令,但将来可能会看看。
【解决方案4】:

Perl 是完成这项任务的完美工具。

保存数据的标题以供将来输出。

提取pos 字段用作哈希键

如果我们之前没有看到pos,则将一行保存到哈希中,否则 将值和名称合并到行中。

一旦所有行处理输出结果(在这种情况下,我使用“格式”并写入)

use strict;
use warnings;
use feature 'say';

my(@pos,%seen,%lines);
my $header = <DATA>;                            # obtain header

chomp $header;

while(<DATA>) {
    next if /^\s*$/;                            # skip empty lines

    chomp;

    my $key = (split '\s+')[1];                 # extract 'pos' to use as $key

    if( $seen{$key} ) {
        my($value,$name) = (split '\s+')[2,4];  # extract value and name
        $lines{$key} =~ s/(\d\s+\S+\s+\S+)/$1,$value/;  # merge value
        $lines{$key} =~ s/$/,$name/;            # merge name
    } else {
        push @pos, $key;                        # preserve order
        $lines{$key} = $_;                      # store lines in a hash
        $seen{$key} = 1;
    }
}


say $header;                                    # output header

my @data;

for (@pos) {                                    # use stored hash 'indexes'
    @data = split '\s+',$lines{$_};             # split into fields
    write;                                      # output
}

# format STDOUT_HEADER = 
# rownbr     pos      pvalue   percentage    samplename
# .

format STDOUT = 
@<<<<< @<<<<<<<<<    @<<<<<<<<  @<<<<<         @<<<<<<<<<<<<
$data[0],$data[1],$data[2],$data[3],$data[4]
.

__DATA__
rownbr     pos      pvalue   percentage    samplename
1      chr1_12000    0.05       5.6            S1
1      chr1_12500    0.04       15.9           S1
3      chr1_12570    0.9        45.3           S2
2      chr1_12500    0.03       13.8           S3

输出

rownbr     pos      pvalue   percentage    samplename
1      chr1_12000    0.05       5.6            S1
1      chr1_12500    0.04,0.03  15.9           S1,S3
3      chr1_12570    0.9        45.3           S2

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-17
    • 2016-12-28
    • 1970-01-01
    • 2014-06-23
    • 2020-09-29
    • 1970-01-01
    • 2019-06-10
    • 2012-10-14
    相关资源
    最近更新 更多