我需要一个 Unix 命令来读取 CSV 文件(超过 700M 行),示例如下:
A, 10
B, 11
C, 12
A, 10
B, 12
D, 10
A, 12
C, 12
该命令将计算第一列中出现的次数,然后计算第二列中不同出现次数的数量,并按第一列中的条目对输出进行分组。这样输出将如下所示:
A, 3, 2
B, 2, 2
C, 2, 1
D, 1, 1
答案1
要获取输出的前两列:
$ cut -d, -f1 <file | sort | uniq -c | awk -vOFS=, '{ print $2, $1 }'
A,3
B,2
C,2
D,1
这会提取原始文件的第一列,对其进行排序并计算重复条目的数量。最后awk
只是交换列并在它们之间插入逗号。
最后一栏可能有
$ sort -u <file | cut -d, -f1 | uniq -c | awk -vOFS=, '{ print $1 }'
2
2
1
1
这会对原始数据进行排序并丢弃重复项。然后提取第一列和重复项的数量那被计算在内。最后awk
仅提取计数。
使用bash
和组合这些paste
:
$ paste -d, <( cut -d, -f1 <file | sort | uniq -c | awk -vOFS=, '{ print $2, $1 }' ) \
<( sort -u <file | cut -d, -f1 | uniq -c | awk -vOFS=, '{ print $1 }' )
A,3,2
B,2,2
C,2,1
D,1,1
如果您对数据进行预先排序,这可能会稍微缩短(并大大加快):
$ sort -o file file
$ paste -d, <( cut -d, -f1 <file | uniq -c | awk -vOFS=, '{ print $2, $1 }' ) \
<( uniq <file | cut -d, -f1 | uniq -c | awk -vOFS=, '{ print $1 }' )
A,3,2
B,2,2
C,2,1
D,1,1
答案2
我想看看是否可以使用 Perl 单行代码来解决这个问题,我能够弄清楚:
$ perl -F, -ane '$lcnt{$F[0]}++; $ccnt{$F[0]}{$F[1]}++; \
END { print "$_, $lcnt{$_}, " . (keys %{ $ccnt{$_} }) . "\n" for sort keys %lcnt }' \
file
A, 3, 2
B, 2, 2
C, 2, 1
D, 1, 1
分解
循环遍历一个文件
这句话可能看起来非常复杂,但一旦你分解它,它实际上非常简单。它的核心是 Perl 中的这种机制:
$ perl -F, -ane '...; END { ... }' file
这告诉 Perl 获取文件file
并循环遍历它,并使用-F,
分隔符自动分割它,完成后,运行该END {..}
块一次并退出。
例如:
$ perl -F, -ane 'print "arg1: $F[0] arg2: $F[1]"; END { print "DONE\n" }' file
arg1: A arg2: 10
arg1: B arg2: 11
arg1: C arg2: 12
arg1: A arg2: 10
arg1: B arg2: 12
arg1: D arg2: 10
arg1: A arg2: 12
arg1: C arg2: 12
DONE
笔记:Perl 的自动拆分功能会自动将列放入数组中@F
,这里我使用元素 1 & 2, $F[0]
& $F[1]
。
数东西
我们需要做的下一件事是计算输入的各个位。为此,我们将利用 Perl 中哈希的力量。我们将使用 2,%lcnt
和%ccnt
.
笔记:Perl 中最烦人的事情之一是定义散列与访问散列时符号的切换。当我们访问它时,我们从 切换%lcnt
到$lcnt["A"]
,但我离题了。
$lcnt{$F[0]}++; $ccnt{$F[0]}{$F[1]}++;
%lcnt
- 第一列的字符数%ccnt
- 包含 2 个坐标的二维哈希来访问第二列的计数
笔记:以这种方式计算事物可以简单地通过我们计算位数的方式来执行独特的功能。
例如,让我们检查%lcnt
哈希的内容:
$ perl -F, -ane '$lcnt{$F[0]}++; $ccnt{$F[0]}{$F[1]}++; \
END { print "key: $_\n" for sort keys %lcnt }' file
key: A
key: B
key: C
key: D
如果我们想查看每个哈希值:
$ perl -F, -ane '$lcnt{$F[0]}++; $ccnt{$F[0]}{$F[1]}++; \
END { print "key: $_ val: $lcnt{$_}\n" for sort keys %lcnt }' file
key: A val: 3
key: B val: 2
key: C val: 2
key: D val: 1
笔记:在这里我们可以看到已经$lcnt{$F[0]}++
完成了所有的辛苦工作数数我们循环遍历文件并将它们添加到 hash 中的每个字符%lcnt
。
这就是结局
最后一个难题是以有用的方式显示所有收集到的信息。为此,我们将在以下位置使用它END {...}
:
print "$_, $lcnt{$_}, " . (keys %{ $ccnt{$_} }) . "\n" for sort keys %lcnt
这将循环遍历键列表%lcnt
并打印以下行:
$_, $lcnt{$_}, " . (keys %{ $ccnt{$_} }) . "\n"
如果很难看出上面的结构,这里有更一般的结构:
A, 3, 2
^--- (keys %{ $ccnt{$_} }) ## count of unique columns for each character ($_)
^------ $lcnt{$_} ## count of each character
^--------- $_ ## character
这将生成一行,其中包含字符 ( $_
)、该字符的计数值 ( $lcnt{$_}
),以及第二列中每个字符的唯一值的计数。
参考
答案3
从命令行运行一个小sqlite3
脚本,input.csv
您的输入数据在哪里:
sqlite3 -batch <<EOF
.mode csv
CREATE TABLE data (letter TEXT, number INTEGER);
.import input.csv data
SELECT letter, COUNT(*) AS lcount, COUNT(DISTINCT number) AS dcount
FROM data
GROUP BY letter ;
EOF
这就像这样工作
$ bash query.sqlite
A,3,2
B,2,2
C,2,1
D,1,1
答案4
datamash -t, -s -g 1 count 1 countunique 2 < input.txt
输入
A, 10
B, 11
C, 12
A, 10
B, 12
D, 10
A, 12
C, 12
输出
A,3,2
B,2,2
C,2,1
D,1,1