【问题标题】:Add comma sepated values inside a column在列内添加逗号分隔值
【发布时间】:2021-11-30 04:15:48
【问题描述】:

您好,我有一个像这样的文件格式 (TSV)

Name  type    Age     Weight       Height 
Xxx   M    12,34,23  50,30,60,70   4,5,6,5.5 
Yxx   F    21,14,32  40,50,20,40   3,4,5,5.5

我想添加年龄、体重和身高中的所有值并在此之后添加一列,然后还有一些百分比,例如 Total_Height/Total_Weight (awk '$0=$0"\t"(NR==1? “百分比”:8 美元/7 美元)')。我的数据集很大,不能用excel做。

像这样

Name  type    Age     Weight       Height     Total_Age Total_Weight Total_Height Percentage
Xxx   M    12,34,23  50,30,60,70   4,5,6,5.5   69        210         20.5          0.097            
Yxx   F    21,14,32  40,50,20,40   3,4,5,5.5   67        150         17.5          0.11 

【问题讨论】:

    标签: python awk


    【解决方案1】:

    使用您显示的示例,请尝试以下代码。

    awk '
    FNR==1{
      print $0,"Total_Age Total_Weight Total_Height Percentage"
      next
    }
    FNR>1{
      totAge=totWeight=totHeight=0
      split($3,tmp,",")
      for(i in tmp){
        totAge+=tmp[i]
      }
      split($4,tmp,",")
      for(i in tmp){
        totWeight+=tmp[i]
      }
      split($5,tmp,",")
      for(i in tmp){
        totHeight+=tmp[i]
      }
      $(NF+1)=totAge
      $(NF+1)=totWeight
      $(NF+1)=totHeight
      $(NF+1)=$(NF-1)==0?"N/A":$NF/$(NF-1)
    }
    1' Input_file | column -t
    

    添加上面awk代码的简短版本:

    awk '
    BEGIN{OFS="\t"}
    FNR==1{
      print $0,"Total_Age Total_Weight Total_Height Percentage"
      next
    }
    FNR>1{
      totAge=totWeight=totHeight=0
      split($3,tmp,",")
      for(i in tmp){
        totAge+=tmp[i]
      }
      split($4,tmp,",")
      for(i in tmp){
        totWeight+=tmp[i]
      }
      split($5,tmp,",")
      for(i in tmp){
        totHeight+=tmp[i]
      }
      $(NF+1)=totAge OFS totWeight OFS totHeight
      $0=$0
      $(NF+1)=( $(NF-1)==0 ? "N/A" : $NF/$(NF-1) )
    }
    1' Input_file | column -t
    

    解释: 简单的解释是,将第 3、4 和 5 列的总和分配给行的最后一列。因此,根据 OP 的要求添加具有最后一列和倒数第二列的除值的列值。使用column -t 使其在输出时看起来更好。

    【讨论】:

    • 在某些情况下,如果所有的值都为零,它会显示错误,如尝试被零除的错误。如何克服这个?可以放 NA 吗?
    • @ZenMac,其实我已经加了$(NF+1)=($NF/$(NF-1)+0)来处理。在这种情况下它会显示0,请您检查一下。
    • (我在你的代码中使用了你的一些 awk 技巧,希望你不要介意,我正在尝试为它写一个函数)
    【解决方案2】:

    在每个 Unix 机器上的任何 shell 中使用任何 awk 并且没有在每个记录中创建新字段(这是低效的,因为它会导致 awk 在每次更改字段时重新构建记录)并且没有更新输入记录(这效率低下,因为它会导致 awk 在您每次更改记录时将记录重新拆分为字段),并且旨在以任何顺序用于任意数量的值输入列:

    $ cat tst.awk
    BEGIN { FS=OFS="\t" }
    { printf "%s%s", $0, OFS }
    NR==1 {
        for (i=3; i<=NF; i++) {
            printf "Total_%s%s", $i, OFS
            tags[i] = $i
        }
        print "Percentage"
        next
    }
    {
        delete tot
        for (i=3; i<=NF; i++) {
            tag = tags[i]
            n = split($i,vals,",")
            for (j in vals) {
                tot[tag] += vals[j]
            }
            printf "%s%s", tot[tag], OFS
        }
        printf "%0.3f%s", (tot["Weight"] ? tot["Height"] / tot["Weight"] : 0), ORS
    }
    

    $ awk -f tst.awk file
    Name    type    Age     Weight  Height  Total_Age       Total_Weight    Total_Height    Percentage
    Xxx     M       12,34,23        50,30,60,70     4,5,6,5.5       69      210     20.5    0.098
    Yxx     F       21,14,32        40,50,20,40     3,4,5,5.5       67      150     17.5    0.117
    

    $ awk -f tst.awk file | column -t
    Name  type  Age       Weight       Height     Total_Age  Total_Weight  Total_Height  Percentage
    Xxx   M     12,34,23  50,30,60,70  4,5,6,5.5  69         210           20.5          0.098
    Yxx   F     21,14,32  40,50,20,40  3,4,5,5.5  67         150           17.5          0.117
    

    为了展示上述方法的功能优势,假设您需要添加更多值,例如 ShoeSize 和/或重新排列列的顺序,例如:

    $ column -t file
    Name  type  ShoeSize  Height     Age       Weight
    Xxx   M     12,8,10   4,5,6,5.5  12,34,23  50,30,60,70
    Yxx   F     9,7,8     3,4,5,5.5  21,14,32  40,50,20,40
    

    现在运行上面的脚本,你会发现Total_ 为每个原始列添加了列,你仍然得到相同的高度/重量Percentage 列添加到末尾:

    $ awk -f tst.awk file | column -t
    Name  type  ShoeSize  Height     Age       Weight       Total_ShoeSize  Total_Height  Total_Age  Total_Weight  Percentage
    Xxx   M     12,8,10   4,5,6,5.5  12,34,23  50,30,60,70  30              20.5          69         210           0.098
    Yxx   F     9,7,8     3,4,5,5.5  21,14,32  40,50,20,40  24              17.5          67         150           0.117
    

    【讨论】:

      【解决方案3】:

      如果您必须多次执行相同的操作,您还可以使用函数对数组值求和(假设这些值是用逗号分隔的数字)。

      重复使用来自@RavinderSingh13 的部分答案,非常感谢@Ed Morton 抽出宝贵时间提供改进代码的出色反馈:

      awk '
      function arraySum(field,      sum,arr,i) {
        split(field,arr,",")
        for (i in arr) sum += arr[i]
        return sum
      }
      FNR==1{
        print $0, "Total_Age", "Total_Weight", "Total_Height", "Percentage"
        next
      }
      NR > 1 {
        sumWeight = arraySum($4)
        sumHeight = arraySum($5)
        print $0, arraySum($3), sumWeight, sumHeight, (sumWeight ? sumHeight/sumWeight : 0)
      }' file | column -t
      

      输出

      Name  type  Age       Weight       Height     Total_Age  Total_Weight  Total_Height  Percentage
      Xxx   M     12,34,23  50,30,60,70  4,5,6,5.5  69         210           20.5          0.097619
      Yxx   F     21,14,32  40,50,20,40  3,4,5,5.5  67         150           17.5          0.116667
      

      【讨论】:

        【解决方案4】:

        我会使用 GNU AWK 的函数 split 来完成这个任务,如下所示。考虑以下简单示例,让file.txt 内容为

        Name  type    Age     Weight       Height 
        Xxx   M    12,34,23  50,30,60,70   4,5,6,5.5 
        Yxx   F    21,14,32  40,50,20,40   3,4,5,5.5
        

        然后

        awk 'BEGIN{OFS="\t"}NR==1{print "Age","Total"}NR>1{totalage=0;split($3,ages,",");for(a in ages){totalage+=ages[a]};print $3,totalage}' file.txt
        

        输出

        Age Total
        12,34,23    69
        21,14,32    67
        

        解释:首先我通知 GNU AWK 使用制表符作为输出字段分隔符 (OFS),然后对于第一行,我打印标题,对于下一行,我:将 totalage 值设置为 0,拆分第三列的内容进入数组ages,,遍历所述数组得到其值的总和,然后print第三列的内容和总和。请注意

        在拆分字符串之前,split() 会删除任何先前存在的 数组 arrayseps 中的元素。

        因此它不像totalage 变量那样需要重置。

        (在 gawk 4.2.1 中测试)

        【讨论】:

        • 您的答案中没有任何内容需要 GNU awk,它在任何 awk 中的行为方式都相同。顺便说一句,你的 gawk 版本已经过时了,你至少应该得到 5.1.0。
        【解决方案5】:

        这是一个 Ruby,它更容易用于多个数据字段,例如:

        ruby -F"\t" -lane '
        if ($.==1) 
            puts "Name\ttype\tAge\tWeight\tHeight\tTotal_Age\tTotal_Weight\tTotal_Height\tPercentage"
            next 
        end
        fields=$F.clone
        $F.each{|f| fields.append(f.split(/,/).map(&:to_f).sum) if f[/^[\d,.]+$/] && f[/,/]}
        fields.append((fields[-1]/fields[-2]).round(3))
        puts fields.join("\t")' file | column -t
        

        打印:

        Name  type  Age       Weight       Height     Total_Age  Total_Weight  Total_Height  Percentage
        Xxx   M     12,34,23  50,30,60,70  4,5,6,5.5  69.0       210.0         20.5          0.098
        Yxx   F     21,14,32  40,50,20,40  3,4,5,5.5  67.0       150.0         17.5          0.117
        

        这里的好处是n.nn,n.nn,...的列总和可以灵活地按照找到的顺序添加到行尾。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-24
          • 2016-03-30
          • 2020-07-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多