CSV 文件中每个字段中以“;”分隔的值列表的最小值

CSV 文件中每个字段中以“;”分隔的值列表的最小值

我有一个很大的 CSV 文件,其中某些单元格中有多个值。如何更改这些单元格,使它们只包含最小值?

例如,给定以下输入:

id,disease_1,disease_2
1001,2008;2009,2009;2010 

笔记

  1. 列/字段分隔符是逗号,,
  2. 每个单元格中的值均以分号分隔,;并按升序排序。
  3. 我想从第 2 列开始我的算法

期望的输出:

id,disease_1,disease_2
1001,2008,2009

答案1

以下是与子命令put一起使用的“表达式”put磨坊主mlr;专门用于处理结构化数据的工具)计算;每个非字段中的 - 分隔值的最小值id

for (key,value in mapexcept($*, "id")) {
    value !=~ ";" { continue }

    var minimum = "";

    for (i,number in splitnv(value, ";")) {
        minimum = min(minimum, number)
    }

    $[key] = minimum;
}

这里我们迭代每条记录中的字段,但如果调用该字段则跳过该字段id。我们跟踪每个字段的最小值,同时迭代以分号分割字段值所产生的数字。在循环结束时,字段的值将被找到的最小值覆盖。不包含的字段;将在循环开始时被跳过。

你可以使用运行它

mlr --csv put -e script file.csv

...script保存上面简短脚本的文件名在哪里,或者您可以在命令行上拼写出来,如下所示:

mlr --csv put 'for (k,v in mapexcept($*,"id")) { v !=~ ";" { continue } var m=""; for (i,n in splitnv(v,";")) { m=min(m,n) } $[k]=m; }' file.csv

考虑到问题中的数据,结果应该是

id,disease_1,disease_2
1001,2008,2009

使用最新版本的 Miller(版本 6+),可以缩短代码显著地通过使用新的sort()get_values()函数:

mlr --csv put 'for (k,v in mapexcept($*,"id")) { $[k] = sort(get_values(splitnv(v,";")))[1] }' file.csv

这会挑选出每个字段的 - 分隔值列表中排在第一位的值;

(谢谢钢铁起子让我意识到这个巧妙的重写。)


如果值已经排序,那么它会变得更加简单和高效:

mlr --csv put 'for (k,v in mapexcept($*,"id")) { $[k] = sub(v,";.*","") }' file.csv

这只是在第一个;字符处截断每个字段。

答案2

以分号分隔的项目始终按升序排列,因此每组的第一个项目是您想要的最小值项目。在此基础上,您可以简单地去除剩余的值:

sed 's/;[^,]*//g' {file}

请注意,这假定您的数据是简单的 CSV 格式,并且不使用也包含分号和逗号的带引号的文本字符串。如果是这种情况,这个基于文本的解决方案无法工作,您将不得不使用更完整的解决方案,例如回答使用磨坊主

示例数据集的输出

id,disease_1,disease_2
1001,2008,2009

答案3

如果是简单的 CSV:

$ perl -MList::Util=min -F, -le 'print join ",", shift@F, map {min split /;/} @F' file.csv
id,disease_1,disease_2
1001,2008,2009

答案4

对于任何 awk 并假设子项尚未按升序排序:

awk '
BEGIN{ FS=OFS="," }
function min(list) {
    subNums=split(list, numbr, /;/)
    min=numbr[1]
    for(n=2; n<=subNums; n++)
        if(numbr[n]<min)
            min=numbr[n]
    return min
}
{
  for(fldNr=2; fldNr<=NF; fldNr++)
      $fldNr=min($fldNr)
}' infile.csv

使用 GNU awk(对于asort()功能对数组进行排序;还PROCINFO["sorted_in"]选项)。

awk '
BEGIN{ FS=OFS=","; PROCINFO["sorted_in"]="@val_num_asc" }
{
  for(fldNr=2; fldNr<=NF; fldNr++)
  {
    subNums=split(fldNr, numArr, /;/)
    asort(numArr)
    $fldNr=numArr[1]
  }
}' infile.csv
  • BEGIN{ FS=OFS="," }将输入和输出字段分隔符设置为“,”。

  • subNums=split(fldNr, numArr, /;/)将当前字段;作为子分隔符进行拆分,并将结果值存储在名为的数组中编号

  • 然后我们用了asort(numArr)来排序编号的值按升序排列。

  • 我们$fldNr=numArr[1]分配排序后的第一个(最小值)值编号数组到当前字段#。

第一个 awk 命令也执行相同的操作,但我们使用 for 循环迭代子数字,通过一次比较每对数字来找出最小值,而不使用任何 GNU awk 扩展。

但是,如果每个字段中的子编号都是按升序排序的,那么您只需选取第一个(最小编号)即可,因此代码会简单得多。

awk '
BEGIN{ FS=OFS="," }
{
  for(fldNr=2; fldNr<=NF; fldNr++)
  {
    split($fldNr, numbr, /;/)
    $fldNr=numbr[1]
    continue
  }
}' infile.csv

相关内容