利用perl操作csv/分号数据
我正在寻找关于如何在单行AWK命令不再足够的情况下操作数据的建议。我正在处理多达1000多行和列的数据集。我遇到了定义太多列变量的问题。我想有一种方法可以使用循环在数组上进行迭代,以定义我要计算和求和的列。我试图根据类似于Excel COUNTIF和SUMIF的键值来计算行的计数和总和利用perl操作csv/分号数据,perl,csv,awk,Perl,Csv,Awk,我正在寻找关于如何在单行AWK命令不再足够的情况下操作数据的建议。我正在处理多达1000多行和列的数据集。我遇到了定义太多列变量的问题。我想有一种方法可以使用循环在数组上进行迭代,以定义我要计算和求和的列。我试图根据类似于Excel COUNTIF和SUMIF的键值来计算行的计数和总和 Data Set Example: Store_Location;Person;Adult_Child;Age;Weight... LocationA;PersonA;0;50;200 LocationB;Per
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,并进行了一些修改(基于使用不同的列):
编辑:
我认为我的逻辑问题是我不想调用字段名/列。因为我想对许多字段执行求和和和计数。成人与儿童的比较只是一个例子。我只想在一个位置列出我想要使用的列。也许解释它的简单方法是,假设输入数据中有100列。我希望能够灵活地确定要分析的列。例如:第15-30列我想根据第1列中的唯一值计算每列的总和和计数。然后可以修改相同的代码,为第15-20列和第30-40列求和。使用AWK,我可以调出我想要使用的列($2、$3、$4,…),但当列太多时,管理起来就很困难。您想要什么并不完全清楚,当然也不清楚您所说的“我在定义太多列变量方面遇到了问题”是什么意思,但我认为您正在尝试这样做,希望它能让你走上正确的道路:
$ 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
$cat文件
仓库位置;人;成人和儿童;年龄;重量
地点a;人格面具0;50;200
地点b;个人b;1.10;100
地点a;个人;1.12;90
地点a;人格面具0;50;200
$cat tst.awk
开始{FS=OFS=“;”}
NR==1{
拆分(0.2亿尼泊尔卢比)
用于(nr=1;nr2nm中的nr;nr++){
nm2nr[nr2nm[nr]]=nr
}
下一个
}
{
门店[$nm2nr[“门店位置”]]
for(nr=3;nr是在Perl中解析和输出分隔数据的极好工具
设置
在解析任何内容之前,我们需要创建一个新的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: $!";
进入以下Perl数据结构:
{
'Age' => '50',
'Adult_Child' => '0',
'Person' => 'PersonA',
'Store_Location' => 'LocationA',
'Weight' => '200'
}
这使我们可以使用人类可读的字符串而不是列号。要使用此功能,我们首先需要告诉解析器每个列使用什么名称。因为我们的数据包含一个带有列名的标题行,所以我们可以使用它:
$csv->column_names( $csv->getline($fh) );
指定要求和的列
我们只需要计算某些列的总和。在您的示例数据中,我们要计算年龄
和重量
列的总和,而不是存储位置
或成人儿童
(成人儿童
基本上是一个布尔标志,因此简单的总和不是我们想要的).让我们创建一个列名数组,以计算其总和:
# 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];
这是我们在上一部分中设置的列名之一。请记住,列号从零开始
一旦我们有了数组,我们就再也不用参考列号了。这意味着,如果我们想要改变我们正在计算的列和,我们只需要改变这一行
我们的输入包含列Age
,但我们希望相应的输出列名为Sum\u of_Age
。我们将把前缀Sum\u of_
放在一个变量中,以便以后可以转换输出:
my $col_prefix = "Sum_of_";
获取CSV数据
现在,我们可以获取数据了。因为我们希望按位置对结果进行分组,所以我们将以位置作为键将计算出的总数存储在散列中:
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;
请注意,我们必须以不同的方式处理成人\儿童
列,因为我们要将单个输入列映射到两个输出列(成人的计数
和儿童的计数
)
{
'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
}
}
open my $fh, ">", "output.csv" or die "Failed to open file for writing: $!";
$csv->print(\*$fh, [ @header ]);
打印结果
现在我们已经计算了所有总计,可以输出结果了。首先,我们需要构造标题行以设置列顺序:
# 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
对象将结果打印到标准输出。这样,我们可以使用与输入文件相同的分号分隔格式。首先,我们打印标题:
$csv->print(\*STDOUT, [ @header ]);
如果要打印到文件而不是标准输出,可以这样做:
{
'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
}
}
open my $fh, ">", "output.csv" or die "Failed to open file for writing: $!";
$csv->print(\*$fh, [ @header ]);
我们将使用@header
数组以正确的列顺序从%totals
散列中获取总计。但是,存储位置
列是特殊的,因为它是%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
使用数据库处理如此多的数据似乎是一个好主意。这不是一个选项吗?我认为我的逻辑问题是我不想调用字段名/列。因为我想对许多字段执行求和和和计数。成人-儿童比较只是一个示例。我只想在1处列出我想使用的列。我我不明白。如果不进行差异编程,您如何将某些字段(如成人\儿童)与其他字段完全区别对待?您是否希望有一个数组将字段名(“数组\儿童”)和给定值(0或1)映射到新的输出字段?这是可以做到的。。。
Store_Location;Count_of_Adults;Count_of_Children;Sum_of_Age;Sum_of_Weight
LocationA;2;1;112;490
LocationB;0;1;10;100