Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/perl/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
利用perl操作csv/分号数据_Perl_Csv_Awk - Fatal编程技术网

利用perl操作csv/分号数据

利用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

我正在寻找关于如何在单行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,并进行了一些修改(基于使用不同的列):

编辑:
我认为我的逻辑问题是我不想调用字段名/列。因为我想对许多字段执行求和和和计数。成人与儿童的比较只是一个例子。我只想在一个位置列出我想要使用的列。也许解释它的简单方法是,假设输入数据中有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