适当的mawk语法> 1000字段文件,用于按列计算非数字数据?

时间:2017-03-01 04:06:28

标签: bash csv awk gnu-parallel

以下愚蠢的硬编码应该是某种循环或并行构造,名义上起作用,但它是糟糕的mawk语法。我的好mawk语法尝试都失败了,在mawk(未显示)和gnu parallel(未显示)中使用for循环。

它确实需要从磁盘读取CSV文件一次,而不是每列读取一次,因为我有一个非常大的CSV文件(数百万行,数千列)。我的原始代码工作正常(未显示),但它为每一列再次读取整个磁盘文件,这需要几个小时,我在意识到发生了什么后将其杀死。我有一个使用GPU连接器插槽的快速固态磁盘,因此该设备上的磁盘读取速度非常快。因此CPU是这里的瓶颈。如果我必须硬编码4000行除了列号之外基本相同的语句,那么代码愚蠢就更是瓶颈了。

代码按列进行计数非数字值。我需要一些循环(for-loop)或并行(首选),因为虽然以下在2列上正常工作,但它不是一种为数千列编写mawk代码的可扩展方法。

tail -n +1 pht.csv | awk -F"," '(($1+0 != $1) && ($1!="")){cnt1++}; (($2+0 != $2) && ($2!="")){cnt2++} END{print cnt1+0; print cnt2+0}'
2
1

"第1列如何处理;第2栏处理;"重复代码减少了吗?如何引入循环?如何引入gnu parallel?非常感谢。我是awk的新手。对其他语言来说并不陌生。

我一直期待以下一个或多个bash命令的一些聪明的组合能够轻松地解决这个问题,但是这里我很多小时后没有任何东西要显示。我伸出双手来。代码贫乏的施舍?

  • seq 1 2(>> 2,用于现实生活中的CSV文件)
  • tail(根据需要跳过标题或不跳过标题)
  • mawk(非常好的行式CSV文件处理,我在我的演示中向您展示了一个方便的语法,可以在一个巨大维度的全数字CSV数据文件中轻松找到非数字)
  • tr(删除对于转置操作很方便的换行符)
  • 切(一次抓一列)
  • parallel(快速好,我有很多内核需要处理的东西,以及phat RAM)

抱歉,我绝对不需要使用像python pandas或R数据帧这样的CSV特定库。我的双手绑在这里。抱歉。谢谢你这么酷。在这种情况下我只能使用bash命令行。

我的mawk可以处理32000+列,所以NF在这里不是问题,不像我见过的其他awk。我的列少于32000(但不是那么多)。

Datafile pht.csv包含以下3x2数据集:

cat pht.csv
8,T1,
T13,3,T1
T13,,-6.350818276405334473e-01

5 个答案:

答案 0 :(得分:3)

无法访问mawk,但您可以执行与此相同的操作

awk -F, 'NR>1 {for(i=1;i<=NF;i++) if($i~/[[:alpha:]]/) a[i]++} 
         END  {for(i=1;i in a;i++) print a[i]}' file
即使是百万条记录,也不会花费超过几分钟的时间。

为了识别指数表示法,正则表达式测试不起作用,您需要恢复到评论中提到的$1+0!=$1测试。请注意,您不必单独检查空字符串。

答案 1 :(得分:3)

到目前为止,所有解决方案都没有并行化。让我们改变它。

假设您有一个串行工作的解决方案,可以从管道中读取:

doit() {
    # This solution gives 5-10 MB/s depending on system
    # Changed so it now also treats '' as zero
    perl -F, -ane 'for(0..$#F) { 
        # Perl has no beautiful way of matching scientific notation
        $s[$_] += $F[$_] !~ /^-?\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?$/m
    }
    END { $" = ","; print "@s\n" }';
}
export -f doit

doit() {
    # Somewhat faster - but regards empty fields as zero
    mawk -F"," '{
        for(i=1;i<=NF;i++) { cnt[i]+=(($i+0)!=$i) && ($i!="") } 
    }
    END { for(i=1;i<NF;i++) printf cnt[i]","; print cnt[NF] }';
}
export -f doit

要并行化这个,我们需要将大文件拆分成块并将每个块传递给串行解决方案:

# This will spawn a process for each core
parallel --pipe-part -a pht.csv --block -1 doit > blocksums

(您需要使用版本20161222或更高版本才能使用&#39; - 阻止-1&#39;)。

要处理标题,我们计算标题的结果,但我们否定结果:

head -n1 pht.csv | doit | perl -pe 's/(^|,)/$1-/g' > headersum

现在我们可以简单总结一下headerum和blockums:

cat headersum blocksums |
  perl -F, -ane 'for(0..$#F) { $s[$_] += $F[$_] }
                 END { $" = ",";print "@s\n" }'

或者如果你喜欢逐行输出:

cat headersum blocksums |
  perl -F, -ane 'for(0..$#F) { $s[$_] += $F[$_] }
                 END { $" = "\n";print "@s\n" }'

答案 2 :(得分:2)

这是你想要做的吗?

$ awk -v RS='[\n,]' '($1+0) != $1' file | sort | uniq -c
      1 T1
      2 T13

以上使用GNU awk进行多字符RS,并且应该在几秒钟内为您描述的输入文件运行。如果你没有GNU awk,你可以这样做:

$ tr ',' $'\n' < file | awk '($1+0) != $1' | sort | uniq -c
      1 T1
      2 T13

我正在避免使用,作为FS的方法,因为那时你必须在循环中使用$i,这会导致awk为每个输入行进行字段拆分时间,但你可以试试:

$ awk -F, '{for (i=1;i<=NF;i++) if (($i+0) != $i) print $i}' file | sort | uniq -c
      1 T1
      2 T13

您可以使用非数字值索引的数组在awk中执行唯一计数,但是您可能必须在内存中存储大量数据(与使用临时交换文件的sort不同所以YMMV采用这种方法。

答案 3 :(得分:0)

我独立解决了它。最后为我做的是以下URL中的动态变量创建示例。 http://cfajohnson.com/shell/cus-faq-2.html#Q24

这是我开发的解决方案。注意:我添加了另一个列,其中包含一些缺失的数据,以便进行更完整的单元测试我不一定是最好的解决方案,也就是TBD。它在我所知道的小csv上正常工作。最好的解决方案还需要在40 GB的csv文件上运行得非常快(未显示哈哈)。

$ cat pht.csv
8,T1,
T13,3,T1
T13,,0

$ tail -n +1 pht.csv | awk -F"," '{ for(i=1;i<=NF;i++) { cnt[i]+=(($i+0)!=$i) && ($i!="") } } END { for(i=1;i<=NF;i++) print cnt[i] }'
2
1
1

PS。老实说,我对自己的答案并不满意。他们说过早优化是万恶之源。那个格言在这里适用。我真的非常希望gnu并行,如果可能的话,而不是for循环,因为我需要速度。

答案 4 :(得分:0)

最后说明:下面我将分享顺序和并行版本的性能时序,以及最佳可用的单元测试数据集。特别感谢Ole Tange在此应用程序中开发代码以使用他的gnu parallel命令。

单元测试数据文件,最终版本:

$ cat pht2.csv
COLA99,COLB,COLC,COLD
8,T1,,T1
T13,3,T1,0.03
T13,,-6.350818276405334473e-01,-0.036

针对逐列非数字计数的顺序版本对大数据(未显示)进行计时:

ga@ga-HP-Z820:/mnt/fastssd$ time tail -n +2 train_all.csv | awk -F"," '{ for(i=1; i<=NF; i++){ cnt[i]+=(($i+0)!=$i) && ($i!="") } } END { for(i=1;i<=NF;i++) print cnt[i] }' > /dev/null

real    35m37.121s

针对逐列非数字计数的并行版本的大数据计时:

# Correctness - 2 1 1 1 is the correct output.
#
# pht2.csv: 2 1 1 1 :GOOD
# train_all.csv: 
# real  1m14.253s
doit1() {
    perl -F, -ane 'for(0..$#F) { 
        # Perl has no beautiful way of matching scientific notation
        $s[$_] += $F[$_] !~ /^-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?$/m
    }
    END { $" = ","; print "@s\n" }';
}

# pht2.csv: 2 1 1 1 :GOOD
# train_all.csv: 
# real  1m59.960s
doit2() {        
    mawk -F"," '{
        for(i=1;i<=NF;i++) { cnt[i]+=(($i+0)!=$i) && ($i!="") } 
    }
    END { for(i=1;i<NF;i++) printf cnt[i]","; print cnt[NF] }';
}

export -f doit1
parallel --pipe-part -a "$fn" --block -1 doit1 > blocksums
if [ $csvheader -eq 1 ] 
then
    head -n1 "$fn" | doit1 | perl -pe 's/(^|,)/$1-/g' > headersum
    cat headersum blocksums | perl -F, -ane 'for(0..$#F) { $s[$_] += $F[$_] } END { $" = "\n";print "@s\n" }' > "$outfile"
else
    cat blocksums | perl -F, -ane 'for(0..$#F) { $s[$_] += $F[$_] } END { $" = "\n";print "@s\n" }' > "$outfile"
fi

新:以下是顺序代码中的ROW方式(非列式):

tail -n +2 train_all.csv | awk -F"," '{ cnt=0; for(i=1; i<=NF; i++){ cnt+=(($i+0)!=$i) && ($i!="") } print cnt; }' > train_all_cnt_nonnumerics_rowwwise.out.txt

背景信息:项目机器学习。这是数据探索的一部分。在使用三星950 Pro SSD存储的双至强32虚拟/ 16物理核心共享内存主机上看到 ~25x并行加速:(32x60)秒连续时间,74秒并行时间。真棒!