我正在寻找有关如何操作1行AWK命令不再足够的数据的建议。我正在处理多达1000多行的数据集。列。我遇到了定义太多列变量的问题。我在想有一种方法可以使用一个循环迭代一个数组,以便可能定义我想要计算的列数。和。我想提出计数&基于键值的行的总和类似于Excel COUNTIF& SUMIF。
Data Set Example:
Store_Location;Person;Adult_Child;Age;Weight...
LocationA;PersonA;0;50;200
LocationB;PersonB;1;10;100
LocationA;PersonC;1;12;90
LocationA;PersonA;0;50;200
Desired Output: (delimiter is not important)
Store_Location;Count_Of_Adults;Count_of_Children;Sum_of_Age;Sum_of_Weight
LocationA;2;1;112;490
LocationB;0;1;10;100
这是我正在使用的示例AWK脚本:
BEGIN {FS=";"} {print "Store_Location;Count_Of_Adults;Count_of_Children;Sum_of_Age;Sum_of_Weight"}
{
n[$1]++;
C1_[$1] += ($3 == "1" ? 0 : 1);S1_[$1] += $4;column_sum3+=$4
C2_[$1] += ($3 == "0" ? 0 : 1);S2_[$1] += $5;column_sum4+=$5
}
END {
for (i in n) {
print i,C1_[i],C2_[i],S1_[i],S2_[i]
}
}
我使用a2p将语法转换为perl并进行了一些修改(基于使用不同的列):
$base = 20;
while (<>){
@array = split(/$FS/, $_, -1);
$n{$array[$base]}++;
$C1_{$array[$base]} += ($array[21] eq '' ? 0 : 1);
$C2_{$array[$base]} += ($array[34] eq '' ? 0 : 1);
$column_count1 += ($array[21] eq '' ? 0 : 1);
$column_count2 += ($array[34] eq '' ? 0 : 1);
$S1_{$array[$base]} += $array[21];
$S2_{$array[$base]} += $array[34];
$column_sum1 += $array[21];
$column_sum2 += $array[34];
}
@sorted_keys = sort { $a <=> $b} keys %n;
foreach $i (@sorted_keys){
print $i,$C1_{$i},$C2_{$i},$S1_{$i},$S2_{$i};
我希望能够做类似的事情,但我试图将我想要的列和我想要计数的列放入不同的数组中。例如:@ sum_array = [1,6,10,15,30]&amp; @count_array = [1,10,20]。并使用循环来创建总和&amp;计数,而不必声明每个输出列。我可以只对每列进行求和并计算,然后打印出我需要的列。我试图使用散列/数组在Perl中编写代码时遇到了困难。我试图使用哈希值,但后来无法获得输出格式,所以我不确定这是否是我想要构建数据的方式。
$n{$array[$base]}{Adult}{count}+= ($array[21] eq 0 ? 0 : 1);
$n{$array[$base]}{Child}{count}+= ($array[21] eq 1 ? 0 : 1);
$n{$array[$base]}{Weight}{sum} += $array[21];
$n{$array[$base]}{Age}{sum}+= $array[34];
编辑: 我认为我的逻辑问题是我不想调出字段名称/列。因为我想要执行总和&amp;算上很多领域。成人儿童比较只是一个例子。我只想在一个地方列出我想要使用的列。也许解释它的简单方法是,假设输入数据有100列。我希望能够灵活地识别我想要分析的列。例如:第15-30栏我想要总和&amp;基于第1列中的唯一值计算每列的数量。然后能够修改相同的代码以获取列15-20和15的总和。 30-40。使用AWK我可以调用我想要使用的列($ 2,$ 3,$ 4,...)但是当列数太多时很难管理。
答案 0 :(得分:1)
目前还不完全清楚你想要什么,而且“我在定义太多列变量时遇到问题”肯定不清楚你是什么意思“但是这就是我认为你想要做的事情,希望它能帮到你在正确的道路上:
$ cat file
Store_Location;Person;Adult_Child;Age;Weight
LocationA;PersonA;0;50;200
LocationB;PersonB;1;10;100
LocationA;PersonC;1;12;90
LocationA;PersonA;0;50;200
$ cat tst.awk
BEGIN{ FS=OFS=";" }
NR==1 {
split($0,nr2nm)
for (nr=1;nr in nr2nm;nr++) {
nm2nr[nr2nm[nr]] = nr
}
next
}
{
stores[$nm2nr["Store_Location"]]
for (nr=3; nr<=NF; nr++) {
fldName = nr2nm[nr]
if ( fldName == "Adult_Child" ) {
fldName = ($nr == 1 ? "Child" : "Adult")
}
fldNames[fldName]
cnt[$nm2nr["Store_Location"],fldName]++
sum[$nm2nr["Store_Location"],fldName] += $nr
}
}
END {
printf "%s", "Store_Location"
for (fldName in fldNames) {
printf ";cnt[%s];sum[%s]", fldName, fldName
}
print ""
for (store in stores) {
printf "%s", store
for (fldName in fldNames) {
printf ";%d;%d", cnt[store,fldName], sum[store,fldName]
}
print ""
}
}
$ awk -f tst.awk file
Store_Location;cnt[Weight];sum[Weight];cnt[Child];sum[Child];cnt[Adult];sum[Adult];cnt[Age];sum[Age]
LocationA;3;490;1;1;2;0;3;112
LocationB;1;100;1;1;0;0;1;10
答案 1 :(得分:1)
Text::CSV是在Perl中解析和输出分隔数据的绝佳工具。让我们运行一个使用Text :: CSV来解决问题的脚本。
在我们解析任何内容之前,我们需要创建一个新的CSV对象并告诉它该分隔符是什么:
use strict; use warnings;
use Text::CSV;
my $csv = Text::CSV->new( { sep_char => ";", eol => $/ } )
or die "Cannot use CSV: " . Text::CSV->error_diag();
我们还需要打开输入文件进行阅读:
open my $fh, "<", "file.csv" or die "Failed to open file for reading: $!";
Text :: CSV可以将每行数据作为hashref获取,列名称为键。例如,我们可以读取行
LocationA;PersonA;0;50;200
进入以下Perl数据结构:
{
'Age' => '50',
'Adult_Child' => '0',
'Person' => 'PersonA',
'Store_Location' => 'LocationA',
'Weight' => '200'
}
这使我们可以使用人类可读的字符串而不是列号。要使用此功能,我们首先需要告诉解析器每列使用什么名称。由于我们的数据包含带有列名的标题行,我们可以使用它:
$csv->column_names( $csv->getline($fh) );
我们只需要计算某些列的总和。在您的示例数据中,我们希望计算Age
和Weight
列的总计,但不计算Store_Location
或Adult_Child
的总计(Adult_Child
本质上是布尔标志所以简单的总和不是我们想要的。让我们创建一个我们想要计算总和的列名数组:
# Use columns 3-4 (zero-indexed)
my @cols_to_sum = @{ [ $csv->column_names() ] }[3..4];
如果您的输入有100列,并且您只想对15-20和30-40列求和,则可以执行以下操作:
my @cols_to_sum = @{ [ $csv->column_names() ] }[15..20,30..40];
这需要我们在上一部分中设置的array slice列名称。请记住,列号从零开始。
一旦我们拥有了数组,我们就不必再次引用列号了。这意味着如果我们想要改变我们要计算总和的列,我们只需要更改这一行。
我们的输入包含列Age
,但我们希望相应的输出列名称为Sum_of_Age
。我们将前缀Sum_of_
放在一个变量中,以便我们稍后可以转换输出:
my $col_prefix = "Sum_of_";
现在我们已准备好获取数据。由于我们希望按位置对结果进行分组,因此我们将计算的总计存储在一个哈希中,并将位置作为键:
my %totals;
while (my $row = $csv->getline_hr($fh)) {
my $location = $row->{Store_Location};
# Add numeric columns to the totals, prepending prefix to each key
foreach my $col (@cols_to_sum) {
my $col_name = $col_prefix . $col;
$totals{$location}{$col_name} += $row->{$col};
}
# Set counts of adults and children to zero if not set for this location
$totals{$location}{Count_of_Adults} //= 0;
$totals{$location}{Count_of_Children} //= 0;
# Handle the adult/child flag
if ($row->{Adult_Child}) {
$totals{$location}{Count_of_Children}++;
}
else {
$totals{$location}{Count_of_Adults}++;
}
}
$csv->eof or $csv->error_diag();
close $fh;
请注意,我们必须以不同方式处理Adult_Child
列,因为我们将单个输入列映射到两个输出列(Count_of_Adults
和Count_of_Children
)。最后,我们的%totals
哈希看起来像这样:
{
'LocationA' => {
'Count_of_Adults' => 2,
'Count_of_Children' => 1,
'Sum_of_Weight' => 490,
'Sum_of_Age' => 112
},
'LocationB' => {
'Count_of_Adults' => 0,
'Count_of_Children' => 1,
'Sum_of_Weight' => 100,
'Sum_of_Age' => 10
}
}
现在我们已经计算了所有总数,我们可以输出结果。首先,我们需要构造标题行来设置列顺序:
# Construct output header, prepending prefix to each "totals" column
my @header = qw(Store_Location Count_of_Adults Count_of_Children);
push @header, $col_prefix . $_ for @cols_to_sum;
我们可以使用相同的Text::CSV
对象将结果打印到stdout。这样我们就可以使用与输入文件相同的分号分隔格式。首先我们打印标题:
$csv->print(\*STDOUT, [ @header ]);
如果要打印到文件而不是stdout,可以这样做:
open my $fh, ">", "output.csv" or die "Failed to open file for writing: $!";
$csv->print(\*$fh, [ @header ]);
我们将使用@header
数组以正确的列顺序从%totals
哈希中获取总计。但是,Store_Location
列是特殊的,因为它是%totals
中的顶级键。我们会将其从@header
数组中删除,以便更轻松地打印结果:
shift @header;
现在我们可以按位置对结果进行排序并打印出来:
foreach my $location (sort keys %totals) {
# Use a hash slice to put result columns in the same order as the header
my $row = [ $location, @{ $totals{$location} }{ @header } ];
$csv->print(\*STDOUT, $row);
}
输出结果为:
Store_Location;Count_of_Adults;Count_of_Children;Sum_of_Age;Sum_of_Weight
LocationA;2;1;112;490
LocationB;0;1;10;100