在列内添加逗号分隔值

Posted

技术标签:

【中文标题】在列内添加逗号分隔值【英文标题】: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 

【问题讨论】:

【参考方案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 '
BEGINOFS="\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 'BEGINOFS="\t"NR==1print "Age","Total"NR>1totalage=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,...的列总和可以灵活地按照找到的顺序添加到行尾。

【讨论】:

以上是关于在列内添加逗号分隔值的主要内容,如果未能解决你的问题,请参考以下文章

如何在列内垂直和水平居中文本? [复制]

在列内检查特定字符串,android studio

扑。如何在列内创建树形图结构

在列内垂直居中 Bootstrap 3 按钮

颤振让孩子在列内匹配另一个孩子的宽度

iview 中table列 一列显示多个数据(后台返回数组显示在列内)