我有一个很大的 CSV 文件,其中某些单元格中有多个值。如何更改这些单元格,使它们只包含最小值?
例如,给定以下输入:
id,disease_1,disease_2
1001,2008;2009,2009;2010
笔记
- 列/字段分隔符是逗号,
,
- 每个单元格中的值均以分号分隔,
;
并按升序排序。 - 我想从第 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
答案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