转置文件并替换缺失值

Hia*_*Sen 5 sed awk perl text-processing

我有来自机器的名称读数,有时这些读数会被复制。

如果未找到读数,则将其留为空白。

Name Instrument Rep R1 R2 R3 
N1 I1 1 1 2 3 
N2 I1 1 1 3 4
N1 I1 2 2 3 4
N3 I1 2 3 4 5
N1 I2 1 1 2 3 
N2 I2 1 1 3 4
N2 I2 2 2 3 4
N3 I2 1 3 4 5
N1 I3 1 1   4  
N2 I3 1 2 5   
N3 I3 1   6 
N3 I3 2     1
Run Code Online (Sandbox Code Playgroud)

首先,我想通过使用它们的平均值(每个名称每个位置)来合并重复。然后,我想转置这些数据并用点 ( .)替换缺失的值。

我想要的输出是

Reading Instrument N1 N2 N3
R1 I1 1.5 1 3
R2 I1 2.5 3 4
R3 I1 3.5 4 5
R1 I2 1 1.5 3  
R2 I2 2 3 4
R3 I2 3 4 5
R1 I3 1 2 .
R2 I3 . 5 6
R3 I3 4 . 1
Run Code Online (Sandbox Code Playgroud)

请注意,名称和读数的数量变化很大,在某些文件中我有 134 个读数,其他一些有 28 个等等,但读数总是从 col3 开始。

这是我为仅一列的测试运行而未成功的尝试

awk '
    NR>1{
        arr[$1" "$2" "$3]   += $4
        count[$1" "$2" "$3] += 1
    }
    END{
        for (a in arr) {
            print a, arr[a] / count[a]
        }
    }
' file |  awk '
NR == 1 {
    n = NF
    for (i = 1; i <= NF; i++)
        row[i] = $i
    next
}
{
    if (NF > n)
        n = NF
    for (i = 1; i <= NF; i++)
        row[i] = row[i] " " $i
}
END {
    for (i = 1; i <= n; i++)
        print row[i]
}' 
Run Code Online (Sandbox Code Playgroud)

msc*_*lli 3

如果你真的想用简单的sed/来做到这一点awk,这确实是可能的:

正如Joe提到的,使用字段分隔符数据值是.SPACEawk

这就是为什么我建议sed首先重新格式化数据:

sed 's/ *$//'删除SPACE行末尾的 s (除了第一行以外的所有输入行都以 结尾SPACE,因此这标准化了输入并删除了每行末尾潜在的缺失值)。

接下来,在每对相邻的 s 之间sed 's/ / . /g/'插入 a (填充不在行尾的潜在缺失值)。.SPACE

SPACE由于这将在相邻缺失值的情况下插入额外的s,sed 's/ / /g'因此必须用于再次删除这些值。

然后,awk可以使用第一行(即标题)来了解读数的名称和数量,在每行末尾添加潜在的缺失值(所有其他值已由 处理sed),对所有读数进行求和并计数跟踪相应的名称和仪器,并以所需的方向/顺序输出平均值(如果有):

sed -e 's/ *$//' -e 's/  / . /g' -e 's/  / /g' <<< 'Name Instrument Rep R1 R2 R3
N1 I1 1 1 2 3
N2 I1 1 1 3 4
N1 I1 2 2 3 4
N3 I1 2 3 4 5
N1 I2 1 1 2 3
N2 I2 1 1 3 4
N2 I2 2 2 3 4
N3 I2 1 3 4 5
N1 I3 1 1   4
N2 I3 1 2 5
N3 I3 1   6
N3 I3 2     1' | awk '

# get number of readings/fields
NR==1{for(i=4;i<=NF;++i)readings[i-4]=$i;fields=NF;next}

# add missing fields in the end
{for(i=NF+1;i<=fields;++i)$i="."}

# keep track of names & instruments
names[$1];instruments[$2]

# sum & count readings per name/instrument (ignoring missing ["."] values)
{for(i=4;i<=NF;++i)if($i!="."){sum[readings[i-4] FS $2 FS $1]+=$i;++count[readings[i-4] FS $2 FS $1]}}

# after reading all data:
END{

  # print header
  printf "Reading"FS"Instrument";for(name in names)printf FS name;print ""

  # sort output rows by instrument
  for(instrument in instruments){

    # keep order of readings
    for(i=0;i<length(readings);++i){

      # print first two columns
      printf readings[i] FS instrument

      # remaining columns (i.e. names):
      for(name in names){

        # if data available:
        if(count[readings[i] FS instrument FS name]){

          # print average
          printf FS sum[readings[i] FS instrument FS name]/count[readings[i] FS instrument FS name]

        # otherwise:
        }else{

          # print missing value ["."]
          printf FS "."
        }

      # proceed with next row
      }print ""
    }
  }
}
'
Run Code Online (Sandbox Code Playgroud)

注意:在我看来,FS在大多数情况下,在多维数组索引中使用作为分隔符是最好的选择,因为所有字段都保证不包含它(如果您必须迭代数组并拆分数组的“维度”)指数)。虽然这里不需要这样做,但我已经养成了习惯。

编辑 指出,在此答案的先前版本中跟踪名称/仪器的方式可能需要一些额外的解释。这启发了上面使用的简化版本:与在不创建此类条目的情况下检查数组中k in a键是否存在不同,将为该条目分配一个空值(并返回它)。ka a[k]

对我来说,上面的代码会产生您要求的输出:

Reading Instrument N1 N2 N3
R1 I1 1.5 1 3
R2 I1 2.5 3 4
R3 I1 3.5 4 5
R1 I2 1 1.5 3
R2 I2 2 3 4
R3 I2 3 4 5
R1 I3 1 2 .
R2 I3 . 5 6
R3 I3 4 . 1
Run Code Online (Sandbox Code Playgroud)

注意:<<<我使用的语法是 HERE-STRING,可能不适用于所有 shell(bash但支持它)。只需将您的输入文件路径传递给它sed,它就应该在所有 shell 中工作(据我所知)。

注意:只有当所有数据都适合内存时,这才有效。如果情况并非如此,则应该有一个内存强度较小的解决方案来首先对输入进行排序来汇总数据。在这种情况下,转置矩阵可能会更加棘手。

编辑:

注意:我的输出在任何行的末尾都不包含任何内容SPACE,与您的示例输出不同,因为我无法弄清楚何时放置 aSPACE以及何时不放置。如果这有任何意义,请调整问题,我会相应更新答案。否则,请考虑SPACE从预期输出中删除这些 s。