转置文件并替换缺失值

转置文件并替换缺失值

我有来自机器的名称读物,有时这些读物是重复的。

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

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

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

我想要的输出是

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

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

这是我尝试仅对一列进行测试运行但未成功的方法

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]
}' 

答案1

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

作为提及经过,用作SPACE字段分隔符&数据价值是一个问题awk

这就是为什么我建议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 ""
    }
  }
}
'

注意:在我看来,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

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

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

编辑:

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

答案2

以下是当前面临的问题:

1) 不能同时使用空格作为字段分隔符和值。如果您的值是固定长度的(每个值一列),那么您可以利用它来发挥您的优势。如果您可以将缺失值设置为零,这会更容易,但在这样的情况下,缺失通常实际上意味着缺失 - 将该项目排除在进一步处理之外。

要开始使用此方法,您需要 $0 包含整个输入行。您可以使用 substr($0, offset, 1) 获取读数,其中偏移量为 7、9,11 或 13(我忘记索引是从 0 还是 1 开始。如果是 0,则从每个偏移量中减去 1) 。

如果它对您的其余逻辑有帮助,您可以用 M 等占位符来替换空白的缺失读数。否则,多个空格与一个空格相同,并且空格后面的任何字段将有效地左移到较低的字段编号。

如果缺失与零相同,那就更容易了。您只需将有问题的空白替换为零,但如果丢失与零不同,这将弄乱您的所有计算。

您可以使用 gsub 替换所有出现的两个连续空格,后跟第三个空格或用“ M ”或“ 0 ”替换行尾。

在当前的第一个 awk 中,您必须在递增和求和之前测试缺失。

2)在你的第二个 awk 中,如果有任何空白缺失值,NF 也可能太小 - 丢弃其他所有内容。

我想我明白你的第一个 awk 的作用,但我不知道你想用第二个 awk 来完成什么。

3)您可能被迫使用点来表示缺失值,以安抚您要将此输出输入到的其他程序,但总的来说,这是一个坏主意,因为它看起来像小数点(这在您的系统中是合法的)数据),并且可能被某些软件解释为零值,或者通常会使其他解析变得更加棘手。

相关内容