【问题标题】:sort two files by header so that they are in matching field order按标题对两个文件进行排序,以便它们处于匹配的字段顺序
【发布时间】:2016-05-18 22:14:21
【问题描述】:

我有两个文件,每个文件都有 700 个字段,其中 699/700 个字段具有匹配的标题。我想重新排序这些字段,以便它们在两个文件中的顺序相同(尽管哪个顺序无关紧要)。例如,给定:

File1:
FRUIT MSMC1 MSMC24 MSMC2 MSMC10
Apple 1 2 3 2
Pear 2 1 4 5

File2:
VEG MSMC24 MSMC1 MSMC2 MSMC10
Onion 2 1 3 2
Radish 0 3 9 3

我希望两个文件都将第一个字段作为两个文件不共有的字段,然后在两个文件中以相同顺序的其余字段,例如一个可能的结果是:

File1:
FRUIT MSMC1 MSMC2 MSMC10 MSMC24
Apple 1 3 2 2
Pear 2 4 5 1

File2:
VEG MSMC1 MSMC2 MSMC10 MSMC24
Onion 1 3 2 2
Radish 3 9 3 0

【问题讨论】:

  • 字段是用空格还是空格分隔的?是否有任何引用或转义,例如"Green Onion" 1 2 3 4 或 Green\ Onion 1 2 3 4

标签: r bash perl awk


【解决方案1】:

使用data.table,这可以帮助你 首先读取文件,

 library(data.table)
 dt1 <- fread("file1.csv")
 dt2 <- fread("file2.csv")

然后,获取字段的名称,常见的

 ndt1 <- names(dt1)[-1]
 ndt2 <- names(dt2)[-1]
 common <- intersect(ndt1, ndt2)

现在您可以应用新订单

 setorder(dt1, c(ndt1[1], setdiff(ndt1, common), common))
 setorder(dt2, c(ndt2[1], setdiff(ndt2, common), common))

【讨论】:

  • 在基础 R 中,只有 common &lt;- intersect(names(dat1),names(dat2)); dat1 &lt;- dat1[c(setdiff(names(dat1),ints),ints)] 等是等价的。
  • 和等效的 dplyr/ggplot library(dplyr); library(ggplot2); as_data_frame(dt1)[ndt1[1] %&gt;% c(setdiff(ndt1, common)) %&gt;% c(common)]
  • 如果一个文件是另一个文件的子集(并非文件 1 中的所有列都在文件 2 中),如何修改这些答案?
  • 你能举个例子吗?该脚本不假定有任何共享列......或相反。如果 1 的所有列都在 2 中,那么您将只有 setdiff(ndt1, common) 为空。
  • @theo4786 查看我的更新答案。包括map 构造。
【解决方案2】:

一种 perl 解决方案,它保留第一个文件原样并写入第二个文件,其中列的排列顺序与第一个文件相同。它读取命令行上提供的 2 个文件(跟在脚本名称后面)。

更新:添加了map $_ // (), 短语以允许第二个文件成为第一个文件的子集。回答他的问题如果一个文件是另一个文件的子集(不是文件 1 中的所有列都在文件 2 中),如何修改这些答案? – theo4786

#!/usr/bin/perl
use strict;
use warnings;

# commandline: perl script_name.pl fruits.csv veg.csv

my (undef, @fruit_hdrs) = split ' ', <> and close ARGV;

my @veg_hdrs;

while (<>) {
    my ($name, @cols) = split;

    # only executes for the first line (header line) of second file
    @veg_hdrs = @cols unless @veg_hdrs;

    my %line;
    @line{ @veg_hdrs } = @cols;

    print join(" ", $name, map $_ // (), @line{ @fruit_hdrs } ), "\n";
}

输出是:

VEG MSMC1 MSMC24 MSMC2 MSMC10
Onion 1 2 3 2
Radish 3 0 9 3

【讨论】:

    【解决方案3】:

    在 perl 中,这个工作的工具是一个散列片。

    您可以使用@hash{@keys} 访问哈希值。

    所以是这样的:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use Data::Dumper;
    
    my @headers; 
    my $type; 
    
    my @rows; 
    
    #iterate data - would do this with a normal 'open'
    while ( <DATA> ) {
      #set headers if the leading word is all upper case 
      if ( m/^[A-Z]+\s/ ) { 
          #seperate out type (VEG/FRUIT) from the other headings. 
          chomp ( ( $type, @headers ) = split ); 
          #print for debugging
          print Dumper \@headers;
      }
      else {
         #create a hash to store this row. 
         my %this_row;
         #split the row on whitespace, capturing name and ordered fields by header row. 
         ( my $name, @this_row{@headers} ) = split; 
         #insert name and type into the hash
         $this_row{name} = $name;
         $this_row{type} = $type;
         #print for debugging
         print Dumper \%this_row;
         #store it in @rows
         push ( @rows, \%this_row ); 
      }
    }
    
    #print output:
    #header line
    print join ("\t", "name", "type", @headers ),"\n";
    #iterate rows, extract ordered by _last_ set of headers. 
    foreach my $row ( @rows ) { 
        print join ( "\t", $row->{name}, $row->{type}, @{$row}{@headers} ),"\n";
    }
    
    __DATA__
    FRUIT MSMC1 MSMC24 MSMC2 MSMC10
    Apple 1 2 3 2
    Pear 2 1 4 5
    VEG MSMC24 MSMC1 MSMC2 MSMC10
    Onion 2 1 3 2
    Radish 0 3 9 3
    

    注意 - 我已使用 Data::Dumper 进行诊断 - 这些行可以删除,但我留下它们是因为说明发生了什么。 同样从&lt;DATA&gt; 读取——通常你会打开一个文件句柄,或者只是使用while ( &lt;&gt; ) { 来读取STDIN 或命令行上指定的文件。

    输出的顺序是基于最后一个标题行'seen' - 您当然可以对其进行排序或重新排序。

    如果您需要处理不匹配的列,这将在缺少的列上出错。在这种情况下,我们可以拆分map 以填充任何空白,并为headers 使用散列以确保我们捕获所有空白。

    例如;

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use Data::Dumper;
    
    my @headers; 
    my %headers_combined; 
    my $type; 
    
    my @rows; 
    
    #iterate data - would do this with a normal 'open'
    while ( <DATA> ) {
      #set headers if the leading word is all upper case 
      if ( m/^[A-Z]+\s/ ) { 
          #seperate out type (VEG/FRUIT) from the other headings. 
          chomp ( ( $type, @headers ) = split ); 
          #add to hash of headers, to preserve uniques
          $headers_combined{$_}++ for @headers; 
          #print for debugging
          print Dumper \@headers;
      }
      else {
         #create a hash to store this row. 
         my %this_row;
         #split the row on whitespace, capturing name and ordered fields by header row. 
         ( my $name, @this_row{@headers} ) = split; 
         #insert name and type into the hash
         $this_row{name} = $name;
         $this_row{type} = $type;
         #print for debugging
         print Dumper \%this_row;
         #store it in @rows
         push ( @rows, \%this_row ); 
      }
    }
    
    #print output:
    #header line
    #note - extract keys from hash, not the @headers array. 
    #sort is needed to order them, because default is unordered. 
    print join ("\t", "name", "type", sort keys %headers_combined ),"\n";
    #iterate rows, extract ordered by _last_ set of headers. 
    foreach my $row ( @rows ) { 
        print join ( "\t", $row->{name}, $row->{type}, map { $row->{$_} // '' } sort keys %headers_combined ),"\n";
    }
    
    __DATA__
    FRUIT MSMC1 MSMC24 MSMC2 MSMC10 OTHER
    Apple 1 2 3 2 x
    Pear 2 1 4 5 y 
    VEG MSMC24 MSMC1 MSMC2 MSMC10 NOTHING
    Onion 2 1 3 2 p
    Radish 0 3 9 3 z
    

    在这里,map { $row-&gt;{$_} // '' } sort keys %headers_combined 获取散列的所有键,按顺序返回它们,然后从行中提取该键 - 如果未定义,则给出一个空格。 (这就是// 所做的)

    【讨论】:

      【解决方案4】:

      这将重新排列 file2 中的字段以匹配 file1 中的顺序:

      $ cat tst.awk
      FNR==1 {
          fileNr++
          for (i=2;i<=NF;i++) {
              name2nr[fileNr,$i] =  i
              nr2name[fileNr,i]  = $i
          }
      }
      fileNr==2 { 
          printf "%s", $1
          for (i=2;i<=NF;i++) {
              printf "%s%s", OFS, $(name2nr[1,nr2name[2,i]])
          }
          print ""
      }
      
      $ awk -f tst.awk file1 file2
      VEG MSMC1 MSMC24 MSMC2 MSMC10
      Onion 1 2 3 2
      Radish 3 0 9 3
      

      使用 GNU awk,您可以删除 fileNr++ 行并在其他任何地方使用 ARGIND 而不是 fileNr。

      【讨论】:

        猜你喜欢
        • 2017-11-13
        • 2012-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-27
        • 2022-07-08
        • 2020-01-16
        相关资源
        最近更新 更多